Search code examples
javadsaapache-commons-codec

Java Exceptions: Inappropriate key specification and short read of DER length


Using Base64 functions from the Apache Commons API and working with DSA I am trying to load a base 64 encoded public key from a file here is the method being used

/**
 * Load a base-64 encoded public key in X.509 format
 * @param pkfile the name of the file containing the public key 
 * @return an instance of PublicKey on success or null on failure
 */
public PublicKey loadBase64PublicKey(String pkfile) { 
    PublicKey pub;
    Base64InputStream bis;
    byte[] buffer;

    // load the contents of the pkfile into the buffer
    try {
        bis = new Base64InputStream(new FileInputStream(pkfile));
        buffer = new byte[bis.available()];
        bis.read(buffer);
        bis.close();
    } catch (Exception e) {
        System.err.println(e.toString());
        return null;
    }

    // use a KeyFactory to parse the data
    try {
        KeyFactory kf = KeyFactory.getInstance("DSA");
        pub = kf.generatePublic(new X509EncodedKeySpec(buffer));
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }

    return pub;
}

Main Method:

public static void main(String args[]) {
    DigitalSignatureA DSA = new DigitalSignatureA();

    // load public key
    PublicKey pubKey;
    pubKey = DSA.loadBase64PublicKey("sign\\pubkey-1.dat");
}   

However when calling the method from main the following error comes up:

java.security.spec.InvalidKeySpecException: Inappropriate key specification: IOException: Short read of DER length
at sun.security.provider.DSAKeyFactory.engineGeneratePublic(Unknown Source)
at java.security.KeyFactory.generatePublic(Unknown Source)
at DigitalSignatureAssignment.loadBase64PublicKey(DigitalSignatureAssignment.java:147)
at DigitalSignatureAssignment.main(DigitalSignatureAssignment.java:224)

Line 147 would be pub = kf.generatePublic(new X509EncodedKeySpec(buffer)); The public key from the file is encoded in X509 but saved under base64 encoding and the Base64InputStream decodes any input.


Solution

  • Java InputStream.available() is never guaranteed to tell you how much (more) data exists

    Note that while some implementations of InputStream will return the total number of bytes in the stream, many will not. It is never correct to use the return value of this method to allocate a buffer intended to hold all data in this stream.

    and in this commons-codec case it doesn't even try

    Returns:
    0 if the InputStream has reached EOF, 1 otherwise

    Either pick a sufficiently large buffer size to start with, or keep expanding it and reading more until EOF, or a combination of both. Or read the file into memory as text (e.g. Files.readAllLines or Files.readAllBytes in j8+) and then decode that in-memory copy (FWIW j8+ now has java.util.Base64 and you don't need commons-codec for this)