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) {
				}
			}
		}
	}
}

用法:参考测试用例

2012年8月28日 | 归档于 技术, 程序
标签: , , , , ,
本文目前尚无任何评论.

发表评论

XHTML: 您可以使用这些标签: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
:wink: :-| :-x :twisted: :) 8-O :( :roll: :-P :oops: :-o :mrgreen: :lol: :idea: :-D :evil: :cry: 8) :arrow: :-? :?: :!: