RSA加密解密工具类
使用Amazon AWS API获取的Windows实例登录密码是经过RSA加密的,为了用Java解出登录密码,研究了一堆资料,参阅了Elasticfox的源码,最终弄出了这个工具类。
几个注意点:
1.RSA的Padding模式非常多,Amazon AWS上使用了PKCS1Padding
2.为了兼顾网络和文件两方面,使用了流获取公钥以及私钥
3.RSA加密解密是在字节上操作,和加密前的内容(一般都是字符串)的编码没有关系,加密前是这些字节,那么解密后也是这些字节,至于字节如何和字符串相互转换,那是使用者的事(网上乱七八糟将字符串混淆进加密解密过程的什么代码!)
4.公钥的格式不是X509EncodedKeySpec!公钥的格式是OpenSSH2,公钥文件内容的解码的实现以及异常处理参考了jsvnserve的代码
5.一定要添加BouncyCastleProvider库
package info.kuyur.demo.util; import java.io.InputStream; import java.math.BigInteger; import java.security.KeyFactory; import java.security.PublicKey; import java.security.Security; import java.security.spec.EncodedKeySpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.RSAPublicKeySpec; import java.util.StringTokenizer; import javax.crypto.Cipher; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.bouncycastle.jce.provider.BouncyCastleProvider; public class RSACipher { private static final String SSH2_RSA_KEY = "ssh-rsa"; private static final String KEY_START_WITH = "-----BEGIN RSA PRIVATE KEY-----"; private static final String KEY_END_WITH = "-----END RSA PRIVATE KEY-----"; public static enum PaddingMode { NO_PADDING, PKCS1_PADDING, OAEP_PADDING } private static final Log log = LogFactory.getLog(RSACipher.class); private RSACipher() {} /** * Encrypt a message. * @param raw data to be encrypted. * @param publicKey * @param paddingMode * @return encrypted data in bytes. */ public static final byte[] encrypt(byte[] raw, byte[] publicKey, PaddingMode paddingMode) throws RSACipherException{ if (publicKey == null) { throw new RSACipherException(RSACipherException.ErrorCode.NULL_PUBLIC_KEY); } try { SSH2DataBuffer buf = new SSH2DataBuffer(publicKey); String type = buf.readString(); if (!SSH2_RSA_KEY.equals(type)) { throw new RSACipherException(RSACipherException.ErrorCode.CORRUPT_OPENSSH2_PUBLIC_KEY); } PublicKey key = RSACipher.decodePublicKey(buf); Security.addProvider(new BouncyCastleProvider()); Cipher cipher = null; if (paddingMode == PaddingMode.NO_PADDING) { cipher = Cipher.getInstance("RSA", "BC"); } else if (paddingMode == PaddingMode.PKCS1_PADDING) { cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding", "BC"); } else if (paddingMode == PaddingMode.OAEP_PADDING) { cipher = Cipher.getInstance("RSA/NONE/OAEPWithSHA1AndMGF1Padding", "BC"); } cipher.init(Cipher.ENCRYPT_MODE, key); return cipher.doFinal(raw); } catch (RSACipherException re) { throw re; } catch (Exception e) { log.error("Encrypt failed.", e); throw new RSACipherException(RSACipherException.ErrorCode.ENCRYPT_FAILURE, e); } } /** * Decrypt a message. * @param raw data encrypted. * @param privateKey * @param paddingMode * @return clear data in bytes. */ public static final byte[] decrypt(byte[] raw, byte[] privateKey, PaddingMode paddingMode) throws RSACipherException { if (privateKey == null) { throw new RSACipherException(RSACipherException.ErrorCode.NULL_PRIVATE_KEY); } try { KeyFactory keyFactory = KeyFactory.getInstance("RSA", new BouncyCastleProvider()); EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey); Security.addProvider(new BouncyCastleProvider()); Cipher cipher = null; if (paddingMode == PaddingMode.NO_PADDING) { cipher = Cipher.getInstance("RSA", "BC"); } else if (paddingMode == PaddingMode.PKCS1_PADDING) { cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding", "BC"); } else if (paddingMode == PaddingMode.OAEP_PADDING) { cipher = Cipher.getInstance("RSA/NONE/OAEPWithSHA1AndMGF1Padding", "BC"); } cipher.init(Cipher.DECRYPT_MODE, keyFactory.generatePrivate(keySpec)); return cipher.doFinal(raw); } catch (InvalidKeySpecException ie) { log.error("Corrupt RSA private key.", ie); throw new RSACipherException(RSACipherException.ErrorCode.CORRUPT_RSA_PRIVATE_KEY, ie); } catch (Exception e) { log.error("Decrypt failed", e); throw new RSACipherException(RSACipherException.ErrorCode.DECRYPT_FAILURE, e); } } /** * Read private key from a stream encoding in UTF-8. * @param stream The input stream of a file or network. * @return raw data of private key */ public static byte[] getPrivateKeyRawData(InputStream stream) throws RSACipherException { if (stream == null) { throw new RSACipherException(RSACipherException.ErrorCode.NULL_SOURCE); } String keyContent = readInputStreamAsString(stream); return getPrivateKeyRawData(keyContent); } public static byte[] getPrivateKeyRawData(byte[] raw) throws RSACipherException { String keyContent = new String(raw); return getPrivateKeyRawData(keyContent); } public static byte[] getPrivateKeyRawData(String keyContent) throws RSACipherException{ if (StringUtils.isEmpty(keyContent)) { throw new RSACipherException(RSACipherException.ErrorCode.NULL_SOURCE); } // Remove header and footer int startIndex = keyContent.indexOf(KEY_START_WITH); int endIndex = keyContent.indexOf(KEY_END_WITH); if (startIndex >= endIndex) { log.error("Invalid RSA private key file format."); throw new RSACipherException(RSACipherException.ErrorCode.INCORRECT_PRIVATE_KEY_FILE_FORMAT); } startIndex += KEY_START_WITH.length(); // Get key string and remove /r/n String key = keyContent.substring(startIndex, endIndex).replaceAll("[^A-Za-z0-9\\+\\/\\=]", ""); // base64 decode return Base64.decodeBase64(key); } /** * Read public key(OpenSSH SSH-2) from a stream encoding in UTF-8. * @param stream The input stream of a file or from network. * @return raw data of public key */ public static byte[] getPublicKeyRawData(InputStream stream) throws RSACipherException { if (stream == null) { throw new RSACipherException(RSACipherException.ErrorCode.NULL_SOURCE); } String keyContent = readInputStreamAsString(stream); if (StringUtils.isEmpty(keyContent)) { throw new RSACipherException(RSACipherException.ErrorCode.NULL_SOURCE); } try { StringTokenizer st = new StringTokenizer(keyContent); st.nextToken(); return Base64.decodeBase64(st.nextToken().getBytes()); } catch (Exception e) { log.error("Invalid OpenSSH2 public key file format.", e); throw new RSACipherException(RSACipherException.ErrorCode.INCORRECT_PUBLIC_KEY_FILE_FORMAT, e); } } public static PublicKey decodePublicKey(SSH2DataBuffer buffer) throws RSACipherException{ final BigInteger e = buffer.readMPint(); final BigInteger n = buffer.readMPint(); try { final KeyFactory factory = KeyFactory.getInstance("RSA"); final RSAPublicKeySpec spec = new RSAPublicKeySpec(n, e); return factory.generatePublic(spec); } catch (Exception ex) { log.error("Decode public key failed", ex); throw new RSACipherException( RSACipherException.ErrorCode.CORRUPT_OPENSSH2_PUBLIC_KEY, ex); } } public static final class SSH2DataBuffer { private final byte[] data; private int pos; public SSH2DataBuffer(final byte[] data) { this.data = data; } public BigInteger readMPint() throws RSACipherException { final byte[] raw = this.readByteArray(); return (raw.length > 0) ? new BigInteger(raw) : BigInteger.valueOf(0); } public String readString() throws RSACipherException { return new String(this.readByteArray()); } private int readUInt32() { final int byte1 = this.data[this.pos++]; final int byte2 = this.data[this.pos++]; final int byte3 = this.data[this.pos++]; final int byte4 = this.data[this.pos++]; return ((byte1 << 24) + (byte2 << 16) + (byte3 << 8) + (byte4 << 0)); } private byte[] readByteArray() throws RSACipherException { final int len = this.readUInt32(); if ((len < 0) || (len > (this.data.length - this.pos))) { throw new RSACipherException(RSACipherException.ErrorCode.CORRUPT_OPENSSH2_PUBLIC_KEY); } final byte[] str = new byte[len]; System.arraycopy(this.data, this.pos, str, 0, len); this.pos += len; return str; } } public static String readInputStreamAsString(InputStream input) { StringBuilder sb = new StringBuilder(); BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); char[] buf = new char[1024]; int numRead = 0; while ((numRead = reader.read(buf)) != -1) { sb.append(buf, 0, numRead); } } catch (IOException e) { log.error("Error while reading file", e); } finally { try { reader.close(); } catch (IOException e) { log.error("Error while closing stream", e); } } return sb.toString(); } public static final class RSACipherException extends RuntimeException { /** * */ private static final long serialVersionUID = 7705600007092255435L; private final ErrorCode erroCode; public RSACipherException(final ErrorCode errorCode) { super(errorCode.message); this.erroCode = errorCode; } public RSACipherException(final ErrorCode errorCode, final Throwable cause) { super(errorCode.message, cause); this.erroCode = errorCode; } public ErrorCode getErrorCode() { return this.erroCode; } public enum ErrorCode { NULL_SOURCE("Null source."), NULL_PUBLIC_KEY("Null public key."), NULL_PRIVATE_KEY("Null private key."), INCORRECT_PUBLIC_KEY_FILE_FORMAT("Incorrect public key file format (OpenSSH2)."), INCORRECT_PRIVATE_KEY_FILE_FORMAT("Incorrect private key file format (RSA)."), CORRUPT_RSA_PRIVATE_KEY("Corrupt RSA private key."), CORRUPT_OPENSSH2_PUBLIC_KEY("Corrupt OpenSSH2 public key."), DECRYPT_FAILURE("Decrypt failure."), ENCRYPT_FAILURE("Encrypt failure.");; private final String message; ErrorCode(final String message) { this.message = message; } } } }
测试用例:
注意点:
1.解密和加密的测试用例中,密文需要自己产生一个来替代原来的。
2.Linux下使用ssh-keygen产生密钥对,放置到测试用的resources目录下
ssh-keygen -b 1024 -t rsa
package info.kuyur.demo.util; import static org.junit.Assert.fail; import java.io.IOException; import java.io.InputStream; import java.security.KeyFactory; import java.security.spec.EncodedKeySpec; import java.security.spec.PKCS8EncodedKeySpec; import javax.crypto.Cipher; import info.kuyur.demo.util.RSACipher.SSH2DataBuffer; import org.apache.commons.codec.binary.Base64; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.Test; public class RSACipherTest { private static String PUBLIC_KEY_FILE = "testpublickey.data"; private static String PRIVATE_KEY_FILE = "testprivatekey.data"; @Test public void testGetPrivateKeyFromFile() { InputStream inputStream = null; try { inputStream = ClassLoader.getSystemResourceAsStream(PRIVATE_KEY_FILE); byte[] key = RSACipher.getPrivateKeyRawData(inputStream); System.out.println("key content:"); for (int i=0; i<key.length; i++) { System.out.print(key[i] + " "); } System.out.println(); Cipher cipher = Cipher.getInstance("RSA", new BouncyCastleProvider()); KeyFactory keyFactory = KeyFactory.getInstance("RSA", new BouncyCastleProvider()); EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key); cipher.init(Cipher.DECRYPT_MODE, keyFactory.generatePrivate(keySpec)); } catch (Exception e) { e.printStackTrace(); fail("Error happen."); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { } } } } @Test public void testGetPublicKeyFromFile() { InputStream inputStream = null; try { inputStream = ClassLoader.getSystemResourceAsStream(PUBLIC_KEY_FILE); byte[] key = RSACipher.getPublicKeyRawData(inputStream); System.out.println("key content:"); for (int i=0; i<key.length; i++) { System.out.print(key[i] + " "); } System.out.println(); SSH2DataBuffer buf = new SSH2DataBuffer(key); String type = buf.readString(); System.out.println(type); Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, RSACipher.decodePublicKey(buf)); } catch (Exception e) { e.printStackTrace(); fail("Error happen."); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { } } } } @Test public void testEncryptAndDecrypt() { InputStream inputStream1 = null; InputStream inputStream2 = null; try { inputStream1 = ClassLoader.getSystemResourceAsStream(PUBLIC_KEY_FILE); byte[] publicKey = RSACipher.getPublicKeyRawData(inputStream1); inputStream2 = ClassLoader.getSystemResourceAsStream(PRIVATE_KEY_FILE); byte[] privateKey = RSACipher.getPrivateKeyRawData(inputStream2); if (publicKey == null || privateKey == null) { fail("Error happen. Key is null."); } String text = "o;ti52D6KYC"; byte[] rawData = text.getBytes("UTF-8"); System.out.println("Text=" + text + "; Bytes="); for (int i=0; i<rawData.length; i++) { System.out.print(rawData[i] + " "); } System.out.println("\nlength=" + rawData.length); System.out.println("\nCipherData="); byte[] cipherData = RSACipher.encrypt(rawData, publicKey, RSACipher.PaddingMode.NO_PADDING); for (int i=0; i<cipherData.length; i++) { System.out.print(cipherData[i] + " "); } System.out.println("\nlength=" + cipherData.length); System.out.println("\nDecryptedData="); byte[] rawData2 = RSACipher.decrypt(cipherData, privateKey, RSACipher.PaddingMode.NO_PADDING); for (int i=0; i<rawData2.length; i++) { System.out.print(rawData2[i] + " "); } } catch (Exception e) { e.printStackTrace(); fail("Error happen."); } finally { if (inputStream1 != null) { try { inputStream1.close(); } catch (Exception e1) {} } if (inputStream2 != null) { try { inputStream2.close(); } catch (Exception e2) {} } } } @Test public void testDecryptAndEncrypt() { InputStream inputStream1 = null; InputStream inputStream2 = null; try { inputStream1 = ClassLoader.getSystemResourceAsStream(PRIVATE_KEY_FILE); byte[] key = RSACipher.getPrivateKeyRawData(inputStream1); if (key == null) { fail("Error happen. Key is null."); } String ciphertext = "qyR0MJPVgsKTgdI71aocnffyg6O3qX7iihEZIi6TVHeoR+91acXIw5GPSr8vsUvEEw93fSusK2fEJjiKozzjfRVhfUe5Np+58MdFH3GFsR87uilbUnp51gqaeTp2cxPtFbOBWxg4PwGIKYV8hhcd72SCg92j0FPgZ4NBQPaXvmR+/8KRSmoU4josR3YgUIyW1AQbb4dGpdTjrE3ghlLpN4A0kmJmMLVbwKXSlcEgSy4iEtRwa1APc7a4CxJ2ihkToFjrtqJeYU/Uzn82FimiUoGChFjIr4uH5az931++3c/BLNA8xMS90OcCa68mnvC2tus+XGZGTYBDV49uPJoIYQ=="; byte[] raw = Base64.decodeBase64(ciphertext); System.out.println("Ciphertext data:"); for (int i=0; i<raw.length; i++) { System.out.print(raw[i] + " "); } System.out.println("\nlength=" + raw.length); byte[] textRaw = RSACipher.decrypt(raw, key, RSACipher.PaddingMode.PKCS1_PADDING); System.out.println("\nDecrypted data="); for (int i=0; i<textRaw.length; i++) { System.out.print(textRaw[i] + " "); } System.out.println("\nlength=" + textRaw.length); System.out.println("Data in ASCII:" + new String(textRaw, "ASCII")); inputStream2 = ClassLoader.getSystemResourceAsStream(PUBLIC_KEY_FILE); byte[] publicKey = RSACipher.getPublicKeyRawData(inputStream2); byte[] reEncrypt = RSACipher.encrypt(textRaw, publicKey, RSACipher.PaddingMode.PKCS1_PADDING); System.out.println("\nCiphertext data in Base64:"); System.out.println(Base64.encodeBase64String(reEncrypt)); } catch (Exception e) { e.printStackTrace(); fail("Error happen."); } finally { if (inputStream1 != null) { try { inputStream1.close(); } catch (IOException e1) { } } if (inputStream2 != null) { try { inputStream2.close(); } catch (IOException e2) { } } } } }
用法:参考测试用例
评论