如何使用 AES 加密创建 Java 密钥库 (.jks) 文件
Posted
技术标签:
【中文标题】如何使用 AES 加密创建 Java 密钥库 (.jks) 文件【英文标题】:How to create Java Key Store (.jks) file with AES encryption 【发布时间】:2012-08-05 13:45:57 【问题描述】:阅读 Oracle 文档,我看到默认情况下 JKS 文件是使用 PBEWithMD5AndTripleDES
加密的。虽然单独的DES让我感到不安,但MD5点亮了一个大红灯。我想使用PBEWithSHA256And256BitAES-CBC-BC
或PBEWithSHA256And128bitAES-CBC-BC
来加密私钥。
我是否必须编写新的 Cryptography Service Provider 来实现整个 KeyStore 接口,或者是否可以对 KeyStore 的创建进行参数化(使用纯 java 或 BouncyCastle)?
编辑: 一点背景知识。
我知道 3DES 并没有被破坏,就像 MD5 用作 KDF(或在 PBE 中)一样。问题是,这就是现在的情况。据我们所知,MD5可能会被打破到明天MD4被打破的水平。我的应用程序寿命至少为 10 年,而且很可能更长。不知何故,在这 10 年之后,我没有看到人们仅仅因为它可能不安全而深入研究工作加密代码。只需查看密码泄露的最后几起重大“事故”,看看发生这种情况的可能性有多大,这对任何看过原始数据库的人来说都是显而易见的事情。
话虽如此:NSA 加密套件 B 仅允许 AES 用于任何类型的对称加密。 NIST 仅列出 HMAC 和 KDF 使用的 SHA-1 和 SHA-2 算法,而不推荐使用 SHA-1。 Suite B 允许仅 SHA-2 哈希函数。这些算法是公开可用的,为什么我不应该使用它们?
【问题讨论】:
TripleDES 不是 DES。 TripleDES is secure. @mikeazo:我知道 DES 和 tripe DES 不是一回事,但即使是 3DES 也最多可以提供 112 位的安全性。至于真正的安全评估:NSA 不同意,Suite B 只允许使用 AES。不过,更大的问题是使用 MD5 和 SHA-1。 JKS(大多数 Java 实用程序和程序在 2017 年开始推广 PKCS12 之前都默认使用它)不使用 MD5&3DES,它使用bogus algorithm invented by Sun back in ITAR days。 JCEKS, which you had to make an effort to select, uses MD5&3DES. 【参考方案1】:最后我选择了使用PBEWithSHA256And256BitAES-CBC-BC
加密的 PKCS#8 文件
加密:
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import org.bouncycastle.asn1.bc.BCObjectIdentifiers;
public class EncodePKCS8
/**
* @param args
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
* @throws NoSuchPaddingException
* @throws InvalidAlgorithmParameterException
* @throws InvalidKeyException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws InvalidParameterSpecException
* @throws IOException
* @throws NoSuchProviderException
*/
public static void main(String[] args) throws NoSuchAlgorithmException,
InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException,
InvalidParameterSpecException, IOException, NoSuchProviderException
// before we can do anything with BouncyCastle we have to register its provider
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
String password = "Very long and complex password";
// generate RSA key pair
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.genKeyPair();
byte[] encryptedPkcs8 = encryptPrivateKey(password, keyPair);
FileOutputStream fos = new FileOutputStream("privkey.p8");
fos.write(encryptedPkcs8);
fos.close();
return;
private static byte[] encryptPrivateKey(String password, KeyPair keyPair)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
InvalidKeySpecException, NoSuchPaddingException,
InvalidAlgorithmParameterException, IllegalBlockSizeException,
BadPaddingException, InvalidParameterSpecException, IOException
int count = 100000; // hash iteration count, best to leave at default or increase
return encryptPrivateKey(password, keyPair, count);
/**
*
* @param password
* @param keyPair
* @param count
* @return PKCS#8 encoded, encrypted keyPair
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
* @throws InvalidKeySpecException
* @throws NoSuchPaddingException
* @throws InvalidKeyException
* @throws InvalidAlgorithmParameterException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
* @throws InvalidParameterSpecException
* @throws IOException
*/
private static byte[] encryptPrivateKey(String password,
KeyPair keyPair, int count) throws NoSuchAlgorithmException,
NoSuchProviderException, InvalidKeySpecException,
NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException,
BadPaddingException, InvalidParameterSpecException, IOException
// extract the encoded private key, this is an unencrypted PKCS#8 private key
byte[] encodedprivkey = keyPair.getPrivate().getEncoded();
// Use a PasswordBasedEncryption (PBE) algorithm, OID of this algorithm will be saved
// in the PKCS#8 file, so changing it (when more standard algorithm or safer
// algorithm is available) doesn't break backwards compatibility.
// In other words, decryptor doesn't need to know the algorithm before it will be
// able to decrypt the PKCS#8 object.
String encAlg = BCObjectIdentifiers.bc_pbe_sha256_pkcs12_aes256_cbc.getId();
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
// Create PBE parameter set
PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, count);
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory keyFac = SecretKeyFactory.getInstance(encAlg, "BC");
SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);
Cipher pbeCipher = Cipher.getInstance(encAlg, "BC");
// Initialize PBE Cipher with key and parameters
pbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);
// Encrypt the encoded Private Key with the PBE key
byte[] ciphertext = pbeCipher.doFinal(encodedprivkey);
// Now construct PKCS #8 EncryptedPrivateKeyInfo object
AlgorithmParameters algparms = AlgorithmParameters.getInstance(encAlg, "BC");
algparms.init(pbeParamSpec);
EncryptedPrivateKeyInfo encinfo = new EncryptedPrivateKeyInfo(algparms, ciphertext);
// DER encoded PKCS#8 encrypted key
byte[] encryptedPkcs8 = encinfo.getEncoded();
return encryptedPkcs8;
解密:
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.interfaces.RSAKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.RSAPublicKeySpec;
import javax.crypto.Cipher;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
public class DecodePKCS8
/**
* @param args
* @throws IOException
* @throws NoSuchPaddingException When file is corrupted
* @throws NoSuchAlgorithmException When no BC provider has been loaded
* @throws InvalidKeySpecException When decryption of file failed
* @throws InvalidAlgorithmParameterException When file is corrupted
* @throws InvalidKeyException When Unlimited cryptography extensions are not installed
*/
public static void main(String[] args) throws
IOException, NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeySpecException, InvalidKeyException, InvalidAlgorithmParameterException
// before we can do anything with BouncyCastle we have to register its provider
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
String password = "Very long and complex password";
// read DER encoded key from files
byte[] encodedprivkey = getFileBytes("privkey.p8");
// this is a encoded PKCS#8 encrypted private key
EncryptedPrivateKeyInfo ePKInfo = new EncryptedPrivateKeyInfo(encodedprivkey);
// first we have to read algorithm name and parameters (salt, iterations) used
// to encrypt the file
Cipher cipher = Cipher.getInstance(ePKInfo.getAlgName());
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory skFac = SecretKeyFactory.getInstance(ePKInfo
.getAlgName());
Key pbeKey = skFac.generateSecret(pbeKeySpec);
// Extract the iteration count and the salt
AlgorithmParameters algParams = ePKInfo.getAlgParameters();
cipher.init(Cipher.DECRYPT_MODE, pbeKey, algParams);
// Decrypt the encryped private key into a PKCS8EncodedKeySpec
KeySpec pkcs8KeySpec = ePKInfo.getKeySpec(cipher);
// Now retrieve the RSA Public and private keys by using an
// RSA key factory.
KeyFactory rsaKeyFac = KeyFactory.getInstance("RSA");
// First get the private key
PrivateKey rsaPriv = rsaKeyFac.generatePrivate(pkcs8KeySpec);
// Now derive the RSA public key from the private key
RSAPublicKeySpec rsaPubKeySpec = new RSAPublicKeySpec(((RSAKey) rsaPriv).getModulus(),
((RSAPrivateCrtKey) rsaPriv).getPublicExponent());
PublicKey rsaPubKey = (RSAPublicKey) rsaKeyFac.generatePublic(rsaPubKeySpec);
System.out.println("Key extracted, public part: " + rsaPubKey);
private static byte[] getFileBytes(String path)
File f = new File(path);
int sizecontent = ((int) f.length()); // no key file will ever be bigger than 4GiB...
byte[] data = new byte[sizecontent];
try
FileInputStream freader = new FileInputStream(f);
freader.read(data, 0, sizecontent) ;
freader.close();
return data;
catch(IOException ioe)
System.out.println(ioe.toString());
return null;
【讨论】:
【参考方案2】:从 Java 8 开始,您可以创建 PKCS#12 密钥库并在存储密钥时传递显式 PasswordProtection 参数以指定要使用的加密算法:
import java.io.FileOutputStream;
import java.security.KeyStore;
import java.security.KeyStore.PasswordProtection;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import javax.crypto.spec.PBEParameterSpec;
public class scratch
public static void main(String... args) throws Exception
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(null, null); // Initialize a blank keystore
// Your key to store
PrivateKey key = ...;
Certificate[] chain = new Certificate[] ... ;
char[] password = "changeit".toCharArray();
byte[] salt = new byte[20];
new SecureRandom().nextBytes(salt);
keyStore.setEntry("test", new PrivateKeyEntry(key, chain),
new PasswordProtection(password,
"PBEWithHmacSHA512AndAES_256",
new PBEParameterSpec(salt, 100_000)));
keyStore.store(new FileOutputStream("/tmp/keystore.p12"), password);
您可以在this article 中阅读更多详细信息(免责声明:我写了那篇文章)。
【讨论】:
【参考方案3】:三重 DES 非常强大,Oracle 可能使用具有 168 位熵的密钥(在撰写本文时提供了完整的 112 位安全性)。
此外,尽管 MD5 可能不安全,例如签名,在 PBE 等密钥派生方案中使用肯定是有效的。
当然,Oracle 及时摆脱这些方案是个好主意,但三重 DES 和 MD5 不应该让您不必要地担心。自己写可能是一个更糟糕的主意,有太多的陷阱。
选择一个好的密码,这可能是您能做的最好的事情。如果您想要高端安全性,或者将您的密钥库放在正确配置的 HSM 或智能卡中。
【讨论】:
我一直在考虑审查和重写 Java 使用的密钥库(添加完整性检查并更好地指示密码是否不正确),但我已经应用密码学超过 11 年了。 虽然 3DES 非常强大,并且作为密钥派生的 MD5 也是如此,但 现在 的情况,而不是未来 10 年的情况(低端估计为我的应用程序生命周期)。 MD5 可能会像明天的 MD4 一样被破坏。它不像以前那样是不可穿透的哈希。此外,NIST 建议我们应该远离 SHA1 作为 MAC 函数,更不用说 MD5... 没错,但是如果您希望我们离开,也许您最好创建一个协议,要求社区对其进行审查、实施并将其发送给 Oracle。这可能比创建自己的私人计划并希望它在野外生存更好。与安全性低于 128 位的算法相比,算法的错误应用更为危险。 我完全同意。我已经阅读了“实用密码学”的副本 :) 这就是为什么我想使用已经使用的标准,只是使用不同的密码。 Bouncy Castle 已经有一个“UBER”jks 密钥库,但它使用 SHA1 和 Twofish。我认为我不会仅仅通过将其密码更改为 AES 和 SHA-2 来使其不安全(它实际上是一个字符串常量的更改)。 不确定 PBE 的 Oracle 实现是否将使用 SHA-2 运行(它不习惯),但我想这将是一个明智的方法。如果它增加了很多安全性,这取决于协议的其余部分,但我想不出它可能会使安全性变得更糟的情况。【参考方案4】:主类
`public class a
/**
* @param args the command line arguments
*/
public static void main(String[] args) throws Exception
// TODO code application logic here
String b = "MSISDN=559915129&productID=5859";
AEScryptography enj = new AEScryptography();
String[] argts = b, "EN";
System.out.println("ENCY -> "+enj.encryptionDecryption(argts));
System.out.println(checksum.encode(b));
`
AES 加密类
`public class AEScryptography
public String encryptionDecryption(String[] args) throws UnsupportedEncodingException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, ShortBufferException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException
String returnVariable = "";
if (args.length == 2)
try
String keystorePass = "201774"; // KeyStroe Password
String keyPass = "mc7129"; // KeyPassword
String alias = "raVi"; // Alias
InputStream keystoreStream = new FileInputStream("D:/keyFile.jks");
KeyStore keystore = KeyStore.getInstance("JCEKS");
keystore.load(keystoreStream, keystorePass.toCharArray());
Key key = keystore.getKey(alias, keyPass.toCharArray());
byte[] bt = key.getEncoded();
String s = new String(bt);
String originalString = args[0];
switch (args[1])
case "EN":
String encryptedString = AES.encrypt(originalString, s, bt);
returnVariable = encryptedString;
break;
case "DE":
String decryptedString = AES.decrypt(originalString, s, bt);
returnVariable = decryptedString;
break;
default:
System.out.println("java -jar /home/jocg/AES-256Encryption.jar StringToEncrypt/Decrypt EN/DE");
break;
catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException ex)
System.out.println(ex);
else
System.out.println("java -jar /home/jocg/AES-256Encryption.jar StringToEncrypt/Decrypt EN/DE");
return returnVariable;
`
AES 类
public class AES
private static SecretKeySpec secretKey;
private static byte[] key;
public static void setKey(String myKey, byte[] ActualKey) throws
NoSuchAlgorithmException
try
key = myKey.getBytes("UTF-8");
key = ActualKey;
secretKey = new SecretKeySpec(key, "AES");
catch (UnsupportedEncodingException e)
public static String encrypt(String strToEncrypt, String secret, byte[] bt) throws UnsupportedEncodingException, NoSuchProviderException, InvalidAlgorithmParameterException
try
setKey(secret, bt);
byte[] iv = new byte[16];
IvParameterSpec ivspec = new IvParameterSpec(iv);
SecretKey secKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secKey, ivspec);
byte[] newData = cipher.doFinal(strToEncrypt.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(newData);
catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e)
System.out.println("Error while encrypting: " + e.toString());
return null;
`
【讨论】:
是的,明天我会用注释更新代码以上是关于如何使用 AES 加密创建 Java 密钥库 (.jks) 文件的主要内容,如果未能解决你的问题,请参考以下文章
创建 AES 密钥比播种 SecureRandom 更好的方法