本文主要介绍Java加密框架(JCA,Java Cryptography Architecture)实践。Java加密框架主要由java.security和javax.crypto两个包下的API提供服务。
3 加密实践
3.1 消息摘要
使用SHA-256算法计算消息原文plainData的消息摘要。
/**
* 生成消息摘要
*
* @param plainData
* @return
*/
public static byte[] generateMessageDigest(byte[] plainData) {
byte[] digest = null;
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
digest = messageDigest.digest(plainData);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return digest;
}
3.2 MAC
如前所述,MAC需要对称加密密钥,生成方法如下:
/**
* 生成SecretKeySpec
*
* @return
*/
public static SecretKeySpec generateSecretKeySpec() {
byte[] keyBytes = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
String algorithm = "RawBytes";
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, algorithm);
return secretKeySpec;
}
使用HmacSHA256算法计算消息原文plainData的MAC消息摘要
/**
* 生成MAC摘要
*
* @param plainData
* @return
*/
public static byte[] generateMacDigest(byte[] plainData) {
byte[] macBytes = null;
try {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec key = KeyUtil.generateSecretKeySpec();
mac.init(key);
macBytes = mac.doFinal(plainData);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
return macBytes;
}
3.3 数字签名
如前所述,数字签名需要公私密钥对,生成方法如下:
/**
* 生成KeyPair
*
* @return
*/
public static KeyPair generateKeyPair(String algorithm) {
KeyPair keyPair = null;
try {
if (algorithm == null || algorithm.isEmpty()) {
algorithm = "DSA";
}
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
keyPair = keyPairGenerator.generateKeyPair();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return keyPair;
}
使用算法SHA1withRSA和密钥对keyPair计算消息原文plainData的数字签名
/**
* 生成数字签名(即用私钥加密的消息摘要)
*
* @param plainData
* @return
*/
public static byte[] generateDigitalSignature(byte[] plainData, KeyPair keyPair) {
byte[] signatureData = null;
try {
Signature signature = Signature.getInstance("SHA1withRSA");
SecureRandom secureRandom = new SecureRandom();
signature.initSign(keyPair.getPrivate(), secureRandom);
signature.update(plainData);
signatureData = signature.sign();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
}
return signatureData;
}
3.4 数字签名校验
使用上一节生成的消息原文plainData和密钥对keyPair校验数字签名signatureData
/**
* 校验数字签名
*
* @param plainData
* @param signatureData
* @return
*/
public static boolean verifyDigitalSignature(byte[] plainData, byte[] signatureData, KeyPair keyPair) {
boolean verified = false;
try {
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initVerify(keyPair.getPublic());
signature.update(plainData);
verified = signature.verify(signatureData);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
}
return verified;
}
3.5 密钥生成与使用
3.5.1 生成与读取keystore文件
(1)使用keytool生成keystore文件
"C:\Program Files\Java\jdk1.6.0_45\bin\keytool" -genkeypair -alias alias -keyalg RSA -keysize 2048 -dname "CN=CN, OU=OU, O=O, L=L, ST=ST, C=CN" -keypass keypass -validity 100 -storetype JKS -keystore keystore.jks -storepass storepass
(2)读取keystore文件
读取keytool生成的keystore文件,导出KeyStore.PrivateKeyEntry类,里面含有公私密钥对和证书。
其中:
keyStoreFileName为keystore.jks
keyStorePassword为storepass
keyStoreAlias为alias
keyPassword为keypass
/**
* 获取KeyStore中的PrivateKeyEntry
*
* @param keyStoreFileName
* @param keyStorePassword
* @param keyStoreAlias
* @param keyPassword
* @return
*/
public static KeyStore.PrivateKeyEntry generatePrivateKeyEntry(String keyStoreFileName, String keyStorePassword, String keyStoreAlias, String keyPassword) {
KeyStore.PrivateKeyEntry privateKeyEntry = null;
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream keyStoreData = new FileInputStream(keyStoreFileName);
char[] keyStorePasswordChar = keyStorePassword.toCharArray();
keyStore.load(keyStoreData, keyStorePasswordChar);
KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(keyPassword.toCharArray());
KeyStore.Entry keyEntry = keyStore.getEntry(keyStoreAlias, entryPassword);
privateKeyEntry = (KeyStore.PrivateKeyEntry) keyEntry;
} catch (Exception e) {
e.printStackTrace();
}
return privateKeyEntry;
}
3.5.2 生成与读取证书
(1)使用keytool生成证书
"C:\Program Files\Java\jdk1.6.0_45\bin\keytool" -exportcert -alias alias -keypass keypass -storetype JKS -keystore keystore.jks -file cert.cert -rfc -storepass storepass
(2)读取证书文件
其中:
certFileName为cert.cert
/**
* 生成证书Certificate
*
* @param certFileName
* @return
*/
public static Certificate generateCertificate(String certFileName) {
Certificate certificate = null;
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
InputStream certificateInputStream = new FileInputStream(certFileName);
certificate = certificateFactory.generateCertificate(certificateInputStream);
} catch (CertificateException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return certificate;
}
3.6 对称加密
(1)生成对称密钥
/**
* 生成对称密钥SecretKey
*
* @return
*/
public static SecretKey generateRandomSecretKey(SecureRandom secureRandom) {
SecretKey secretKey = null;
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
if (secureRandom == null) {
secureRandom = new SecureRandom();
}
int keyBitSize = 256;
keyGenerator.init(keyBitSize, secureRandom);
secretKey = keyGenerator.generateKey();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return secretKey;
}
(2)使用对称密钥加密
其中plainText为消息原文,key为上一步生成的对称密钥,iv为初始化向量
IvParameterSpec iv = new IvParameterSpec(secureRandom.generateSeed(16));
/**
* 使用对称密钥加密
*
* @return
*/
public static byte[] encryptRandomKey(byte[] plainText, Key key, IvParameterSpec iv) {
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = null;
byte[] cipherText = null;
try {
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
cipherText = cipher.doFinal(plainText);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
return cipherText;
}
(3)使用对称密钥解密
其中cipherText为消息密文,key为第一步生成的对称密钥,iv为初始化向量
IvParameterSpec iv = new IvParameterSpec(secureRandom.generateSeed(16));
/**
* 使用对称密钥解密
*
* @return
*/
public static byte[] decryptRandomKey(byte[] cipherText, Key key, IvParameterSpec iv) {
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = null;
byte[] plainText = null;
try {
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, key, iv);
plainText = cipher.doFinal(cipherText);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
return plainText;
}
3.7 非对称加密
(1)使用openSSH生成非对称密钥对
1. 生成2048-bit RSA 私钥
$ openssl genrsa -out private_key.pem 2048
2. 将私钥转为PKCS#8 格式(使Java程序能导入)
$ openssl pkcs8 -topk8 -inform PEM -outform DER -in private_key.pem -out private_key.der –nocrypt
3. 将公钥导出为DER格式(使Java程序能导入)
$ openssl rsa -in private_key.pem -pubout -outform DER -out public_key.der
这样,在当前目录下产生了公私钥对private_key.der和public_key.der
(2)读取公钥
其中:fileName为第一步生成的public_key.der
/**
* 读取openSSL生成的RSA公钥(der格式)
*
* @param fileName
* @return
*/
public static PublicKey readPublicKey(String fileName) {
PublicKey publicKey = null;
InputStream inputStream = null;
try {
File file = new File(fileName);
byte[] keyBytes = new byte[(int) file.length()];
inputStream = new FileInputStream(file);
inputStream.read(keyBytes);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
publicKey = keyFactory.generatePublic(spec);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return publicKey;
}
(3)使用公钥加密
使用上一步获取的公钥key对消息原文plainText进行加密。
/**
* 使用密钥加密
*
* @return
*/
public static byte[] encryptFixKey(byte[] plainText, Key key) {
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = null;
byte[] cipherText = null;
try {
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, key);
cipherText = cipher.doFinal(plainText);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return cipherText;
}
(4)读取私钥
其中,fileName为第一步生成的private_key.der
/**
* 读取openSSL生成的RSA私钥(PKCS#8格式)
*
* @param fileName
* @return
*/
public static PrivateKey readPrivateKey(String fileName) {
PrivateKey privateKey = null;
InputStream inputStream = null;
try {
File file = new File(fileName);
byte[] keyBytes = new byte[(int) file.length()];
inputStream = new FileInputStream(file);
inputStream.read(keyBytes);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
privateKey = keyFactory.generatePrivate(spec);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return privateKey;
}
(5)使用私钥解密
使用上一步获取的私钥key对加密文本cipherText进行解密,可以获取第二步的消息原文。
/**
* 使用密钥解密
*
* @return
*/
public static byte[] decryptFixKey(byte[] cipherText, Key key) {
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = null;
byte[] plainText = null;
try {
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, key);
plainText = cipher.doFinal(cipherText);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return plainText;
}