Mercurial > 510Connectbot
view src/ch/ethz/ssh2/crypto/PEMDecoder.java @ 274:82bd20cb0d1d ganymed
start conversion from trilead to ganymed
author | Carl Byington <carl@five-ten-sg.com> |
---|---|
date | Fri, 18 Jul 2014 11:32:32 -0700 |
parents | 91a31873c42a |
children | 03ae56b26003 |
line wrap: on
line source
/* * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. * Please refer to the LICENSE.txt for licensing details. */ package ch.ethz.ssh2.crypto; import java.io.BufferedReader; import java.io.CharArrayReader; import java.io.IOException; import java.math.BigInteger; import java.security.DigestException; import ch.ethz.ssh2.crypto.cipher.AES; import ch.ethz.ssh2.crypto.cipher.BlockCipher; import ch.ethz.ssh2.crypto.cipher.CBCMode; import ch.ethz.ssh2.crypto.cipher.DES; import ch.ethz.ssh2.crypto.cipher.DESede; import ch.ethz.ssh2.crypto.digest.MD5; import ch.ethz.ssh2.signature.DSAPrivateKey; import ch.ethz.ssh2.signature.RSAPrivateKey; import ch.ethz.ssh2.util.StringEncoder; /** * PEM Support. * * @author Christian Plattner * @version $Id: PEMDecoder.java 154 2014-04-28 11:45:02Z dkocher@sudo.ch $ */ public class PEMDecoder { private static final int PEM_RSA_PRIVATE_KEY = 1; private static final int PEM_DSA_PRIVATE_KEY = 2; private static int hexToInt(char c) { if ((c >= 'a') && (c <= 'f')) { return (c - 'a') + 10; } if ((c >= 'A') && (c <= 'F')) { return (c - 'A') + 10; } if ((c >= '0') && (c <= '9')) { return (c - '0'); } throw new IllegalArgumentException("Need hex char"); } private static byte[] hexToByteArray(String hex) { if (hex == null) throw new IllegalArgumentException("null argument"); if ((hex.length() % 2) != 0) throw new IllegalArgumentException("Uneven string length in hex encoding."); byte decoded[] = new byte[hex.length() / 2]; for (int i = 0; i < decoded.length; i++) { int hi = hexToInt(hex.charAt(i * 2)); int lo = hexToInt(hex.charAt((i * 2) + 1)); decoded[i] = (byte) (hi * 16 + lo); } return decoded; } private static byte[] generateKeyFromPasswordSaltWithMD5(byte[] password, byte[] salt, int keyLen) throws IOException { if (salt.length < 8) throw new IllegalArgumentException("Salt needs to be at least 8 bytes for key generation."); MD5 md5 = new MD5(); byte[] key = new byte[keyLen]; byte[] tmp = new byte[md5.getDigestLength()]; while (true) { md5.update(password, 0, password.length); md5.update(salt, 0, 8); // ARGH we only use the first 8 bytes of the salt in this step. // This took me two hours until I got AES-xxx running. int copy = (keyLen < tmp.length) ? keyLen : tmp.length; try { md5.digest(tmp, 0); } catch(DigestException e) { throw new IOException(e); } System.arraycopy(tmp, 0, key, key.length - keyLen, copy); keyLen -= copy; if (keyLen == 0) return key; md5.update(tmp, 0, tmp.length); } } private static byte[] removePadding(byte[] buff, int blockSize) throws IOException { /* Removes RFC 1423/PKCS #7 padding */ int rfc_1423_padding = buff[buff.length - 1] & 0xff; if ((rfc_1423_padding < 1) || (rfc_1423_padding > blockSize)) throw new PEMDecryptException("Decrypted PEM has wrong padding, did you specify the correct password?"); for (int i = 2; i <= rfc_1423_padding; i++) { if (buff[buff.length - i] != rfc_1423_padding) throw new PEMDecryptException("Decrypted PEM has wrong padding, did you specify the correct password?"); } byte[] tmp = new byte[buff.length - rfc_1423_padding]; System.arraycopy(buff, 0, tmp, 0, buff.length - rfc_1423_padding); return tmp; } private static PEMStructure parsePEM(char[] pem) throws IOException { PEMStructure ps = new PEMStructure(); String line = null; BufferedReader br = new BufferedReader(new CharArrayReader(pem)); String endLine = null; while (true) { line = br.readLine(); if (line == null) throw new IOException("Invalid PEM structure, '-----BEGIN...' missing"); line = line.trim(); if (line.startsWith("-----BEGIN DSA PRIVATE KEY-----")) { endLine = "-----END DSA PRIVATE KEY-----"; ps.pemType = PEM_DSA_PRIVATE_KEY; break; } if (line.startsWith("-----BEGIN RSA PRIVATE KEY-----")) { endLine = "-----END RSA PRIVATE KEY-----"; ps.pemType = PEM_RSA_PRIVATE_KEY; break; } } while (true) { line = br.readLine(); if (line == null) throw new IOException("Invalid PEM structure, " + endLine + " missing"); line = line.trim(); int sem_idx = line.indexOf(':'); if (sem_idx == -1) break; String name = line.substring(0, sem_idx + 1); String value = line.substring(sem_idx + 1); String values[] = value.split(","); for (int i = 0; i < values.length; i++) values[i] = values[i].trim(); // Proc-Type: 4,ENCRYPTED // DEK-Info: DES-EDE3-CBC,579B6BE3E5C60483 if ("Proc-Type:".equals(name)) { ps.procType = values; continue; } if ("DEK-Info:".equals(name)) { ps.dekInfo = values; continue; } /* Ignore line */ } StringBuilder keyData = new StringBuilder(); while (true) { if (line == null) throw new IOException("Invalid PEM structure, " + endLine + " missing"); line = line.trim(); if (line.startsWith(endLine)) break; keyData.append(line); line = br.readLine(); } char[] pem_chars = new char[keyData.length()]; keyData.getChars(0, pem_chars.length, pem_chars, 0); ps.data = Base64.decode(pem_chars); if (ps.data.length == 0) throw new IOException("Invalid PEM structure, no data available"); return ps; } private static void decryptPEM(PEMStructure ps, byte[] pw) throws IOException { if (ps.dekInfo == null) throw new IOException("Broken PEM, no mode and salt given, but encryption enabled"); if (ps.dekInfo.length != 2) throw new IOException("Broken PEM, DEK-Info is incomplete!"); String algo = ps.dekInfo[0]; byte[] salt = hexToByteArray(ps.dekInfo[1]); BlockCipher bc = null; if (algo.equals("DES-EDE3-CBC")) { DESede des3 = new DESede(); des3.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24)); bc = new CBCMode(des3, salt, false); } else if (algo.equals("DES-CBC")) { DES des = new DES(); des.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 8)); bc = new CBCMode(des, salt, false); } else if (algo.equals("AES-128-CBC")) { AES aes = new AES(); aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 16)); bc = new CBCMode(aes, salt, false); } else if (algo.equals("AES-192-CBC")) { AES aes = new AES(); aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24)); bc = new CBCMode(aes, salt, false); } else if (algo.equals("AES-256-CBC")) { AES aes = new AES(); aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 32)); bc = new CBCMode(aes, salt, false); } else { throw new IOException("Cannot decrypt PEM structure, unknown cipher " + algo); } if ((ps.data.length % bc.getBlockSize()) != 0) throw new IOException("Invalid PEM structure, size of encrypted block is not a multiple of " + bc.getBlockSize()); /* Now decrypt the content */ byte[] dz = new byte[ps.data.length]; for (int i = 0; i < ps.data.length / bc.getBlockSize(); i++) { bc.transformBlock(ps.data, i * bc.getBlockSize(), dz, i * bc.getBlockSize()); } /* Now check and remove RFC 1423/PKCS #7 padding */ dz = removePadding(dz, bc.getBlockSize()); ps.data = dz; ps.dekInfo = null; ps.procType = null; } public static final boolean isPEMEncrypted(PEMStructure ps) throws IOException { if (ps.procType == null) return false; if (ps.procType.length != 2) throw new IOException("Unknown Proc-Type field."); if ("4".equals(ps.procType[0]) == false) throw new IOException("Unknown Proc-Type field (" + ps.procType[0] + ")"); if ("ENCRYPTED".equals(ps.procType[1])) return true; return false; } public static final boolean isPEMEncrypted(char[] pem) throws IOException { return isPEMEncrypted(parsePEM(pem)); } public static Object decode(char[] pem, String password) throws IOException { PEMStructure ps = parsePEM(pem); if (isPEMEncrypted(ps)) { if (password == null) throw new IOException("PEM is encrypted, but no password was specified"); decryptPEM(ps, StringEncoder.GetBytes(password)); } if (ps.pemType == PEM_DSA_PRIVATE_KEY) { SimpleDERReader dr = new SimpleDERReader(ps.data); byte[] seq = dr.readSequenceAsByteArray(); if (dr.available() != 0) throw new IOException("Padding in DSA PRIVATE KEY DER stream."); dr.resetInput(seq); BigInteger version = dr.readInt(); if (version.compareTo(BigInteger.ZERO) != 0) throw new IOException("Wrong version (" + version + ") in DSA PRIVATE KEY DER stream."); BigInteger p = dr.readInt(); BigInteger q = dr.readInt(); BigInteger g = dr.readInt(); BigInteger y = dr.readInt(); BigInteger x = dr.readInt(); if (dr.available() != 0) throw new IOException("Padding in DSA PRIVATE KEY DER stream."); return new DSAPrivateKey(p, q, g, y, x); } if (ps.pemType == PEM_RSA_PRIVATE_KEY) { SimpleDERReader dr = new SimpleDERReader(ps.data); byte[] seq = dr.readSequenceAsByteArray(); if (dr.available() != 0) throw new IOException("Padding in RSA PRIVATE KEY DER stream."); dr.resetInput(seq); BigInteger version = dr.readInt(); if ((version.compareTo(BigInteger.ZERO) != 0) && (version.compareTo(BigInteger.ONE) != 0)) throw new IOException("Wrong version (" + version + ") in RSA PRIVATE KEY DER stream."); BigInteger n = dr.readInt(); BigInteger e = dr.readInt(); BigInteger d = dr.readInt(); return new RSAPrivateKey(d, e, n); } throw new IOException("PEM problem: it is of unknown type"); } }