CryptoUtils.java

  1. /*
  2.  * CryptoUtils
  3.  *
  4.  * $Id$
  5.  * $URL$
  6.  */
  7. package gov.usgs.util;

  8. import java.io.ByteArrayInputStream;
  9. import java.io.ByteArrayOutputStream;
  10. import java.io.IOException;
  11. import java.io.InputStream;
  12. import java.io.OutputStream;
  13. import java.math.BigInteger;
  14. import java.nio.ByteBuffer;
  15. import java.security.Key;
  16. import java.security.PublicKey;
  17. import java.security.PrivateKey;
  18. import java.security.cert.Certificate;
  19. import java.security.cert.CertificateException;
  20. import java.security.cert.CertificateFactory;
  21. import java.security.interfaces.DSAPrivateKey;
  22. import java.security.interfaces.DSAPublicKey;
  23. import java.security.interfaces.RSAPrivateKey;
  24. import java.security.interfaces.RSAPublicKey;
  25. import java.security.spec.DSAPublicKeySpec;
  26. import java.security.spec.DSAPrivateKeySpec;
  27. import java.security.spec.InvalidKeySpecException;
  28. import java.security.spec.MGF1ParameterSpec;
  29. import java.security.spec.PKCS8EncodedKeySpec;
  30. import java.security.spec.PSSParameterSpec;
  31. import java.security.spec.RSAPublicKeySpec;
  32. import java.security.spec.RSAPrivateKeySpec;
  33. import java.security.spec.X509EncodedKeySpec;
  34. import java.security.KeyFactory;
  35. import java.security.KeyPair;
  36. import java.security.KeyPairGenerator;
  37. import java.security.Signature;
  38. import java.security.SignatureException;
  39. import java.security.InvalidAlgorithmParameterException;
  40. import java.security.InvalidKeyException;
  41. import java.security.NoSuchAlgorithmException;
  42. import java.util.Base64;
  43. import java.util.LinkedList;
  44. import java.util.List;

  45. import javax.crypto.Cipher;
  46. import javax.crypto.CipherOutputStream;
  47. import javax.crypto.KeyGenerator;
  48. import javax.crypto.NoSuchPaddingException;

  49. import ch.ethz.ssh2.crypto.PEMDecoder;

  50. /**
  51.  * Encryption and signing utilities.
  52.  */
  53. public class CryptoUtils {

  54.     /** Algorithm used by AES keys and ciphers. */
  55.     public static final String AES_ALGORITHM = "AES";
  56.     /** Number of bits for AES 128 bit key. */
  57.     public static final int AES_128 = 128;
  58.     /** Number of bits for AES 256 bit key. */
  59.     public static final int AES_256 = 256;

  60.     /** Algorithm used by DSA keys. */
  61.     public static final String DSA_ALGORITHM = "DSA";
  62.     /** Algorithm used for signature with DSA key. */
  63.     public static final String DSA_SIGNATURE_ALGORITHM = "SHA1withDSA";
  64.     /** Number of bits for DSA 1024 bit key. */
  65.     public static final int DSA_1024 = 1024;

  66.     /** Algorithm used by RSA keys and ciphers. */
  67.     public static final String RSA_ALGORITHM = "RSA";
  68.     /** Algorithm used for signature with RSA key. */
  69.     public static final String RSA_SIGNATURE_ALGORITHM = "SHA1withRSA";
  70.     /** Number of bits for RSA 2048 bit key. */
  71.     public static final int RSA_2048 = 2048;
  72.     /** Number of bits for RSA 4096 bit key. */
  73.     public static final int RSA_4096 = 4096;

  74.     /** Signature versions. */
  75.     public enum Version {
  76.         /** Signature enum for v1 */
  77.         SIGNATURE_V1("v1"),
  78.         /** Signature enum for v2 */
  79.         SIGNATURE_V2("v2");

  80.         private String value;

  81.         Version(final String value) {
  82.             this.value = value;
  83.         }

  84.         public String toString() {
  85.             return this.value;
  86.         }

  87.         /**
  88.          * @param value to get a signature from
  89.          * @return a version
  90.          * @throws IllegalArgumentException if unknown version.
  91.          */
  92.         public static Version fromString(final String value) {
  93.             if (SIGNATURE_V1.value.equals(value)) {
  94.                 return SIGNATURE_V1;
  95.             } else if (SIGNATURE_V2.value.equals(value)) {
  96.                 return SIGNATURE_V2;
  97.             } else {
  98.                 throw new IllegalArgumentException("Invalid signature version");
  99.             }
  100.         }
  101.     }

  102.     /** v2 Algorithm for DSA signature */
  103.     public static final String SIGNATURE_V2_DSA_ALGORITHM = "SHA256withDSA";
  104.     /** v2 Algorithm for RSA signature */
  105.     public static final String SIGNATURE_V2_RSA_ALGORITHM = "RSASSA-PSS";

  106.     /**
  107.      * Process a data stream using a cipher.
  108.      *
  109.      * If cipher is initialized to ENCRYPT_MODE, the input stream will be
  110.      * encrypted. If cipher is initialized to DECRYPT_MODE, the input stream
  111.      * will be decrypted.
  112.      *
  113.      * @param cipher
  114.      *            an initialized cipher.
  115.      * @param in
  116.      *            the data to encrypt.
  117.      * @param out
  118.      *            where encrypted data is written.
  119.      * @throws NoSuchAlgorithmException if invalid encrypt/decrypt algorithm
  120.      * @throws NoSuchPaddingException on padding error
  121.      * @throws InvalidKeyException if key is not RSA or DSA.
  122.      * @throws IOException if IO error occurs
  123.      */
  124.     public static void processCipherStream(final Cipher cipher,
  125.             final InputStream in, final OutputStream out)
  126.             throws NoSuchAlgorithmException, NoSuchPaddingException,
  127.             InvalidKeyException, IOException {
  128.         CipherOutputStream cos = new CipherOutputStream(out, cipher);
  129.         StreamUtils.transferStream(in, cos);
  130.     }

  131.     /**
  132.      * Create and initialize an encrypting cipher using key.getAlgorithm() as
  133.      * transformation.
  134.      *
  135.      * @param key
  136.      *            the key used to encrypt.
  137.      * @return a cipher used to encrypt.
  138.      * @throws NoSuchAlgorithmException on invalid algorithm
  139.      * @throws NoSuchPaddingException on invalid padding
  140.      * @throws InvalidKeyException if key is not RSA or DSA.
  141.      */
  142.     public static Cipher getEncryptCipher(final Key key)
  143.             throws NoSuchAlgorithmException, NoSuchPaddingException,
  144.             InvalidKeyException {
  145.         Cipher cipher = Cipher.getInstance(key.getAlgorithm());
  146.         cipher.init(Cipher.ENCRYPT_MODE, key);
  147.         return cipher;
  148.     }

  149.     /**
  150.      * Create and initialize a decrypting cipher using key.getAlgorithm as
  151.      * transformation.
  152.      *
  153.      * @param key
  154.      *            the key used to decrypt.
  155.      * @return a cipher used to decrypt.
  156.      * @throws NoSuchAlgorithmException on invalid algorithm
  157.      * @throws NoSuchPaddingException on invalid padding
  158.      * @throws InvalidKeyException if key is not RSA or DSA.
  159.      */
  160.     public static Cipher getDecryptCipher(final Key key)
  161.             throws NoSuchAlgorithmException, NoSuchPaddingException,
  162.             InvalidKeyException {
  163.         Cipher cipher = Cipher.getInstance(key.getAlgorithm());
  164.         cipher.init(Cipher.DECRYPT_MODE, key);
  165.         return cipher;
  166.     }

  167.     /**
  168.      * Create and configure a signature object based on key type.
  169.      *
  170.      * @param key
  171.      *     Key used to sign/verify.
  172.      * @param version
  173.      *     SIGNATURE_V1 or SIGNATURE_V2
  174.      * @return
  175.      *     Configured Signature object
  176.      * @throws InvalidKeyException
  177.      *     if key is not RSA or DSA.
  178.      * @throws NoSuchAlgorithmException
  179.      *     on invalid algorithm
  180.      * @throws SignatureException
  181.      *     on signature error
  182.      */
  183.     public static Signature getSignature(final Key key, final Version version)
  184.             throws InvalidKeyException, NoSuchAlgorithmException,
  185.                     SignatureException {
  186.         Signature signature = null;
  187.         if (version == Version.SIGNATURE_V1) {
  188.             if (key instanceof DSAPrivateKey || key instanceof DSAPublicKey) {
  189.                 signature = Signature.getInstance(DSA_SIGNATURE_ALGORITHM);
  190.             } else if (key instanceof RSAPrivateKey || key instanceof RSAPublicKey) {
  191.                 signature = Signature.getInstance(RSA_SIGNATURE_ALGORITHM);
  192.             }
  193.         } else if (version == Version.SIGNATURE_V2) {
  194.             if (key instanceof DSAPrivateKey || key instanceof DSAPublicKey) {
  195.                 signature = Signature.getInstance(SIGNATURE_V2_DSA_ALGORITHM);
  196.             } else if (key instanceof RSAPrivateKey || key instanceof RSAPublicKey) {
  197.                 signature = Signature.getInstance(SIGNATURE_V2_RSA_ALGORITHM);
  198.             }
  199.         } else {
  200.             throw new IllegalArgumentException("Unexpected signature version " + version);
  201.         }
  202.         if (signature == null) {
  203.             throw new InvalidKeyException("Expected DSA or RSA key");
  204.         }
  205.         return signature;
  206.     }

  207.     /**
  208.      *
  209.      * @param key Key used to sign/verify.
  210.      * @param version SIGNATURE_V1 or SIGNATURE_V2
  211.      * @param signature A signature
  212.      * @throws InvalidAlgorithmParameterException
  213.      *     on invalid or inappropriate algorithm parameters
  214.      */
  215.     public static void configureSignature(final Key key, final Version version,
  216.             final Signature signature) throws InvalidAlgorithmParameterException {
  217.         if (version == Version.SIGNATURE_V2
  218.                 && (key instanceof RSAPrivateKey || key instanceof RSAPublicKey)) {
  219.             int keySize;
  220.             if (key instanceof RSAPrivateKey) {
  221.                 keySize = ((RSAPrivateKey)key).getModulus().bitLength();
  222.             } else {
  223.                 keySize = ((RSAPublicKey)key).getModulus().bitLength();
  224.             }
  225.             // match python cryptography calculation:
  226.             // https://github.com/pyca/cryptography/blob/b16561670320c65a18cce41d0db0c42ab68350a9/src/cryptography/hazmat/primitives/asymmetric/padding.py#L73
  227.             // 32 = (sha)256 / 8
  228.             int maxSaltLength = ((keySize + 6) / 8) - 32 - 2;
  229.             signature.setParameter(
  230.                     new PSSParameterSpec("SHA-256", "MGF1",
  231.                             MGF1ParameterSpec.SHA256, maxSaltLength, 1));
  232.         }
  233.     }

  234.     /**
  235.      * A convenience method that chooses a signature algorithm based on the key
  236.      * type. Works with DSA and RSA keys.
  237.      *
  238.      * @param privateKey a private key
  239.      * @param data data to sign
  240.      * @return signature as hex encoded string
  241.      * @throws InvalidAlgorithmParameterException
  242.      *     on invalid or inappropriate algorithm parameters
  243.      * @throws InvalidKeyException
  244.      *     if key is not RSA or DSA.
  245.      * @throws NoSuchAlgorithmException
  246.      *     on invalid algorithm
  247.      * @throws SignatureException
  248.      *     on signature error
  249.      */
  250.     public static String sign(final PrivateKey privateKey, final byte[] data)
  251.             throws InvalidAlgorithmParameterException, InvalidKeyException,
  252.             NoSuchAlgorithmException, SignatureException {
  253.         // use v1 by default
  254.         return sign(privateKey, data, Version.SIGNATURE_V1);
  255.     }

  256.     /**
  257.      * Generate a signature.
  258.      *
  259.      * @param privateKey
  260.      *            private key to use, should be acceptable by signature
  261.      *            instance.
  262.      * @param data
  263.      *            data/hash to sign.
  264.      * @param version
  265.      *            the signature version.
  266.      * @return signature as hex encoded string.
  267.      * @throws InvalidAlgorithmParameterException
  268.      *            on invalid or inappropriate algorithm parameters
  269.      * @throws NoSuchAlgorithmException
  270.      *            on invalid algorithm
  271.      * @throws InvalidKeyException
  272.      *            if key is not RSA or DSA.
  273.      * @throws SignatureException
  274.      *            on signature error
  275.      */
  276.     public static String sign(final PrivateKey privateKey, final byte[] data,
  277.             final Version version) throws InvalidAlgorithmParameterException,
  278.             InvalidKeyException, NoSuchAlgorithmException, SignatureException {
  279.         final Signature signature = getSignature(privateKey, version);
  280.         signature.initSign(privateKey);
  281.         configureSignature(privateKey, version, signature);
  282.         signature.update(data);
  283.         return Base64.getEncoder().encodeToString(signature.sign());
  284.     }

  285.     /**
  286.      * A convenience method that chooses a signature algorithm based on the key
  287.      * type. Works with DSA and RSA keys.
  288.      *
  289.      * @param publicKey
  290.      *            public key corresponding to private key that generated
  291.      *            signature.
  292.      * @param data
  293.      *            data/hash to verify
  294.      * @param allegedSignature
  295.      *            to try and verify with
  296.      * @return boolean
  297.      * @throws InvalidAlgorithmParameterException
  298.      *            on invalid or inappropriate algorithm parameters
  299.      * @throws InvalidKeyException
  300.      *            if key is not RSA or DSA.
  301.      * @throws NoSuchAlgorithmException
  302.      *            on invalid algorithm
  303.      * @throws SignatureException
  304.      *            on signature error
  305.      */
  306.     public static boolean verify(final PublicKey publicKey, final byte[] data,
  307.             final String allegedSignature)
  308.             throws InvalidAlgorithmParameterException, InvalidKeyException,
  309.             NoSuchAlgorithmException, SignatureException {
  310.         return verify(publicKey, data, allegedSignature, Version.SIGNATURE_V1);
  311.     }

  312.     /**
  313.      * Verify a signature.
  314.      *
  315.      * @param publicKey
  316.      *            public key corresponding to private key that generated
  317.      *            signature.
  318.      * @param data
  319.      *            the data/hash that was signed.
  320.      * @param allegedSignature
  321.      *            the signature being verified.
  322.      * @param version
  323.      *            signature version.
  324.      * @return true if computed signature matches allegedSignature.
  325.      * @throws InvalidAlgorithmParameterException
  326.      *            on invalid or inappropriate algorithm parameters
  327.      * @throws NoSuchAlgorithmException
  328.      *            on invalid algorithm
  329.      * @throws InvalidKeyException
  330.      *            if key is not RSA or DSA.
  331.      * @throws SignatureException
  332.      *            on signature error
  333.      */
  334.     public static boolean verify(final PublicKey publicKey, final byte[] data,
  335.             final String allegedSignature, final Version version)
  336.             throws InvalidAlgorithmParameterException, InvalidKeyException,
  337.             NoSuchAlgorithmException, SignatureException {
  338.         final Signature signature = getSignature(publicKey, version);
  339.         signature.initVerify(publicKey);
  340.         configureSignature(publicKey, version, signature);
  341.         signature.update(data);
  342.         return signature.verify(Base64.getDecoder().decode(allegedSignature));
  343.     }

  344.     /**
  345.      * A convenience method to encrypt a byte array.
  346.      *
  347.      * @param key
  348.      *            a key that can be used to encrypt.
  349.      * @param toEncrypt
  350.      *            the data to encrypt.
  351.      * @return encrypted byte array.
  352.      * @throws InvalidKeyException
  353.      *            if key is not RSA or DSA.
  354.      * @throws NoSuchAlgorithmException
  355.      *            on invalid algorithm
  356.      * @throws NoSuchPaddingException
  357.      *            on invalid padding
  358.      * @throws IllegalArgumentException
  359.      *            on illegal args passed to function
  360.      * @throws IOException
  361.      *            on IO error
  362.      */
  363.     public static byte[] encrypt(final Key key, final byte[] toEncrypt)
  364.             throws InvalidKeyException, NoSuchAlgorithmException,
  365.             NoSuchPaddingException, IllegalArgumentException, IOException {
  366.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  367.         processCipherStream(getEncryptCipher(key),
  368.                 StreamUtils.getInputStream(toEncrypt), baos);
  369.         return baos.toByteArray();
  370.     }

  371.     /**
  372.      * A convenience method to decrypt a byte array.
  373.      *
  374.      * @param key
  375.      *            a key that can be used to decrypt.
  376.      * @param toDecrypt
  377.      *            the data to decrypt.
  378.      * @return decrypted byte array.
  379.      * @throws InvalidKeyException
  380.      *            if key is not RSA or DSA.
  381.      * @throws NoSuchAlgorithmException
  382.      *            on invalid algorithm
  383.      * @throws NoSuchPaddingException
  384.      *            on invalid padding
  385.      * @throws IllegalArgumentException
  386.      *            on illegal args passed to function
  387.      * @throws IOException
  388.      *            on IO error
  389.      */
  390.     public static byte[] decrypt(final Key key, final byte[] toDecrypt)
  391.             throws InvalidKeyException, NoSuchAlgorithmException,
  392.             NoSuchPaddingException, IllegalArgumentException, IOException {
  393.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  394.         processCipherStream(getDecryptCipher(key),
  395.                 StreamUtils.getInputStream(toDecrypt), baos);
  396.         return baos.toByteArray();
  397.     }

  398.     /**
  399.      * Generate a new symmetric encryption key.
  400.      *
  401.      * @param bits
  402.      *            how many bits. This should be AES_128 or AES256.
  403.      * @return generated AES key.
  404.      * @throws NoSuchAlgorithmException
  405.      *            on invalid algorithm
  406.      */
  407.     public static Key generateAESKey(final int bits)
  408.             throws NoSuchAlgorithmException {
  409.         KeyGenerator gen = KeyGenerator.getInstance(AES_ALGORITHM);
  410.         gen.init(bits);
  411.         return gen.generateKey();
  412.     }

  413.     /**
  414.      * Generate a new asymmetric encryption key pair.
  415.      *
  416.      * @param bits
  417.      *            how many bits. Must be a valid RSA size.
  418.      * @return generated RSA key pair.
  419.      * @throws NoSuchAlgorithmException
  420.      *            on invalid algorithm
  421.      */
  422.     public static KeyPair generateRSAKeyPair(final int bits)
  423.             throws NoSuchAlgorithmException {
  424.         KeyPairGenerator gen = KeyPairGenerator.getInstance(RSA_ALGORITHM);
  425.         gen.initialize(bits);
  426.         return gen.generateKeyPair();
  427.     }

  428.     /**
  429.      * Generate a new asymmetric signature key pair.
  430.      *
  431.      * @param bits
  432.      *            how many bits. Must be a valid DSA size.
  433.      * @return generated DSA key pair.
  434.      * @throws NoSuchAlgorithmException
  435.      *            on invalid algorithm
  436.      */
  437.     public static KeyPair generateDSAKeyPair(final int bits)
  438.             throws NoSuchAlgorithmException {
  439.         KeyPairGenerator gen = KeyPairGenerator.getInstance(DSA_ALGORITHM);
  440.         gen.initialize(bits);
  441.         return gen.generateKeyPair();
  442.     }

  443.     /**
  444.      * Read a X509 encoded certificate. May be DER or PEM encoded.
  445.      *
  446.      * @param bytes
  447.      *            the certificate data as a byte array.
  448.      * @return parsed certificate.
  449.      * @throws CertificateException
  450.      *            on certificate issue
  451.      * @throws IOException
  452.      *            on IO error
  453.      */
  454.     public static Certificate readCertificate(final byte[] bytes)
  455.             throws CertificateException, IOException {
  456.         byte[] data = bytes;
  457.         if (((char) data[0]) == '-') {
  458.             data = convertPEMToDER(new String(data));
  459.         }
  460.         Certificate certificate = CertificateFactory.getInstance("X.509")
  461.                 .generateCertificate(new ByteArrayInputStream(data));
  462.         return certificate;
  463.     }

  464.     /**
  465.      * Read a X509 encoded public key. May be DER or PEM encoded.
  466.      *
  467.      * @param bytes
  468.      *            the key data as a byte array.
  469.      * @return parsed public key.
  470.      * @throws IOException
  471.      *            on IO error
  472.      * @throws NoSuchAlgorithmException
  473.      *            on invalid algorithm
  474.      */
  475.     public static PublicKey readPublicKey(final byte[] bytes)
  476.             throws IOException, NoSuchAlgorithmException {
  477.         byte[] data = bytes;
  478.         // decode from PEM format
  479.         if (((char) data[0]) == '-') {
  480.             data = convertPEMToDER(new String(data));
  481.         }
  482.         X509EncodedKeySpec spec = new X509EncodedKeySpec(data);

  483.         try {
  484.             return KeyFactory.getInstance(DSA_ALGORITHM).generatePublic(spec);
  485.         } catch (InvalidKeySpecException e) {
  486.             try {
  487.                 return KeyFactory.getInstance(RSA_ALGORITHM).generatePublic(
  488.                         spec);
  489.             } catch (InvalidKeySpecException e2) {
  490.                 // ignore
  491.             }
  492.         }

  493.         return null;
  494.     }

  495.     /**
  496.      * Read a PKCS#8 encoded private key. May be DER or PEM encoded.
  497.      *
  498.      * @param bytes
  499.      *            the key data as a byte array.
  500.      * @return parsed private key.
  501.      * @throws IOException
  502.      *            on IO error
  503.      * @throws NoSuchAlgorithmException
  504.      *            on invalid algorithm
  505.      */
  506.     public static PrivateKey readPrivateKey(final byte[] bytes)
  507.             throws IOException, NoSuchAlgorithmException {
  508.         byte[] data = bytes;
  509.         // decode from PEM format
  510.         if (((char) data[0]) == '-') {
  511.             data = convertPEMToDER(new String(data));
  512.         }
  513.         PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(data);

  514.         try {
  515.             return KeyFactory.getInstance(DSA_ALGORITHM).generatePrivate(spec);
  516.         } catch (InvalidKeySpecException e) {
  517.             try {
  518.                 return KeyFactory.getInstance(RSA_ALGORITHM).generatePrivate(
  519.                         spec);
  520.             } catch (InvalidKeySpecException e2) {
  521.                 // ignore
  522.             }
  523.         }

  524.         return null;
  525.     }

  526.     /**
  527.      * Read an OpenSSH private key from a stream.
  528.      *
  529.      * @param bytes
  530.      *            the byte array containing an OpenSSH private key.
  531.      * @param password
  532.      *            password if the key is encrypted.
  533.      * @return decoded PrivateKey.
  534.      * @throws IOException
  535.      *            on IO error
  536.      * @throws InvalidKeySpecException
  537.      *            when key has invalid specifications
  538.      * @throws NoSuchAlgorithmException
  539.      *            on invalid algorithm
  540.      */
  541.     public static PrivateKey readOpenSSHPrivateKey(final byte[] bytes,
  542.             final String password) throws IOException,
  543.             NoSuchAlgorithmException, InvalidKeySpecException {
  544.         PrivateKey key = null;

  545.         // this returns an ethz DSAPrivateKey or RSAPrivateKey
  546.         Object obj = PEMDecoder.decode(new String(bytes).toCharArray(),
  547.                 password);

  548.         if (obj instanceof ch.ethz.ssh2.signature.DSAPrivateKey) {
  549.             ch.ethz.ssh2.signature.DSAPrivateKey ethzDSAKey = (ch.ethz.ssh2.signature.DSAPrivateKey) obj;
  550.             key = (DSAPrivateKey) KeyFactory.getInstance("DSA")
  551.                     .generatePrivate(
  552.                             new DSAPrivateKeySpec(ethzDSAKey.getX(), ethzDSAKey
  553.                                     .getP(), ethzDSAKey.getQ(), ethzDSAKey
  554.                                     .getG()));
  555.         } else if (obj instanceof ch.ethz.ssh2.signature.RSAPrivateKey) {
  556.             ch.ethz.ssh2.signature.RSAPrivateKey ethzRSAKey = (ch.ethz.ssh2.signature.RSAPrivateKey) obj;
  557.             key = (RSAPrivateKey) KeyFactory.getInstance("RSA")
  558.                     .generatePrivate(
  559.                             new RSAPrivateKeySpec(ethzRSAKey.getN(), ethzRSAKey
  560.                                     .getD()));
  561.         }

  562.         return key;
  563.     }

  564.     /**
  565.      * Read an OpenSSH PublicKey from a stream.
  566.      *
  567.      * @param bytes
  568.      *            bytes to read.
  569.      * @return a publicKey
  570.      * @throws IOException
  571.      *            on IO error
  572.      * @throws NoSuchAlgorithmException
  573.      *            on invalid algorithm
  574.      * @throws InvalidKeySpecException
  575.      *            when key has invalid specifications
  576.      */
  577.     public static PublicKey readOpenSSHPublicKey(final byte[] bytes)
  578.             throws IOException, InvalidKeySpecException,
  579.             NoSuchAlgorithmException {

  580.         // format is <type><space><base64data><space><comment>
  581.         String[] line = new String(bytes).trim().split(" ", 3);
  582.         String type = line[0];
  583.         String content = line[1];
  584.         // String comment = line[2];

  585.         ByteBuffer buf = ByteBuffer.wrap(Base64.getDecoder().decode(content));

  586.         // format of decoded content is: <type><keyparams>
  587.         // where type and each param is a DER string
  588.         String decodedType = new String(readDERString(buf));
  589.         if (!decodedType.equals(type)) {
  590.             throw new IllegalArgumentException("expected " + type + ", got "
  591.                     + decodedType);
  592.         }
  593.         if (type.equals("ssh-dss")) {
  594.             // dsa key params are p, q, g, y
  595.             BigInteger p = new BigInteger(readDERString(buf));
  596.             BigInteger q = new BigInteger(readDERString(buf));
  597.             BigInteger g = new BigInteger(readDERString(buf));
  598.             BigInteger y = new BigInteger(readDERString(buf));
  599.             return KeyFactory.getInstance(DSA_ALGORITHM).generatePublic(
  600.                     new DSAPublicKeySpec(y, p, q, g));
  601.         } else if (type.equals("ssh-rsa")) {
  602.             // rsa key params are e, y
  603.             BigInteger e = new BigInteger(readDERString(buf));
  604.             BigInteger y = new BigInteger(readDERString(buf));
  605.             return KeyFactory.getInstance(RSA_ALGORITHM).generatePublic(
  606.                     new RSAPublicKeySpec(y, e));
  607.         } else {
  608.             throw new InvalidKeySpecException("Unknown key type '" + type + "'");
  609.         }
  610.     }

  611.     /**
  612.      * This method reads a DER encoded byte string from a ByteBuffer.
  613.      *
  614.      * A DER encoded string has
  615.      *
  616.      * length = 4 bytes big-endian integer<br>
  617.      * string = length bytes
  618.      *
  619.      * @param buf
  620.      *            buffer containing DER encoded bytes.
  621.      * @return bytes the decoded bytes.
  622.      */
  623.     public static byte[] readDERString(ByteBuffer buf) {
  624.         int length = buf.getInt();
  625.         if (length > 8192) {
  626.             throw new IllegalArgumentException("DER String Length " + length
  627.                     + " > 8192");
  628.         }
  629.         byte[] bytes = new byte[length];
  630.         buf.get(bytes);
  631.         return bytes;
  632.     }

  633.     /**
  634.      * Read a PEM format.
  635.      *
  636.      * This does not currently support encrypted PEM formats.
  637.      *
  638.      * @param string
  639.      *            string containing PEM formatted data.
  640.      * @return DER formatted data.
  641.      * @throws IOException
  642.      *            on IO error
  643.      */
  644.     public static byte[] convertPEMToDER(final String string)
  645.             throws IOException {
  646.         List<String> lines = StringUtils.split(string, "\n");
  647.         String header = lines.remove(0);
  648.         String footer = lines.remove(lines.size() - 1);
  649.         String type;

  650.         if (header.startsWith("-----BEGIN ") && header.endsWith("-----")) {
  651.             type = header;
  652.             type = type.replace("-----BEGIN ", "");
  653.             type = type.replace("-----", "");

  654.             if (type.contains("ENCRYPTED")) {
  655.                 throw new IllegalArgumentException(
  656.                         "Encrypted keys are not supported.");
  657.             }

  658.             if (footer.equals("-----END " + type + "-----")) {
  659.                 // expected match
  660.                 return Base64.getMimeDecoder().decode(
  661.                         StringUtils.join(new LinkedList<Object>(lines), "\n"));
  662.             } else {
  663.                 throw new IllegalArgumentException("Unexpected PEM footer '"
  664.                         + footer + "'");
  665.             }
  666.         } else {
  667.             throw new IllegalArgumentException("Unexpected PEM header '"
  668.                     + header + "'");
  669.         }
  670.     }

  671. }