如何使用 AES 加密创建 Java Key Store (.jks) 文件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11864696/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 06:42:45  来源:igfitidea点击:

How to create Java Key Store (.jks) file with AES encryption

javacryptographybouncycastlejks

提问by Hubert Kario

Reading Oracle documentation, I see that by default JKS files are encrypted using PBEWithMD5AndTripleDES. While DES alone makes me feel uneasy, MD5 lights a big red light. I'd like to use PBEWithSHA256And256BitAES-CBC-BCor PBEWithSHA256And128bitAES-CBC-BCto encrypt private keys.

阅读 Oracle 文档,我看到默认情况下 JKS 文件使用PBEWithMD5AndTripleDES. 虽然只有DES让我感到不安,但MD5亮起了大红灯。我想使用PBEWithSHA256And256BitAES-CBC-BCPBEWithSHA256And128bitAES-CBC-BC加密私钥。

Do I have to write new Cryptography Service Provider implementing whole KeyStore interface or is it possible to parametrise the creation of KeyStore (either using plain java or BouncyCastle)?

我是否必须编写新的加密服务提供程序来实现整个 KeyStore 接口,或者是否可以参数化 KeyStore 的创建(使用纯 java 或 BouncyCastle)?

EDIT:A little bit of background.

编辑:一点背景。

I know that 3DES isn't broken, just as is MD5 used as KDF (or in PBE). The problem is, that this is the situation for now. For all we know, MD5 may be broken to the level MD4 is broken tomorrow. My application life is at least 10 years, and it's very likely it's much more. Somehow I don't see people after those 10 years delving deep into workingcrypto code just because it may not be secure. One just needs to look at last few of the big "mishaps" with password leaks to see how likely is that, and that were obvious things to anyone that saw the raw database.

我知道 3DES 没有损坏,就像 MD5 用作 KDF(或在 PBE 中)一样。问题是,这是局面现在。据我们所知,MD5可能会被打破到明天MD4被打破的水平。我的应用寿命至少为 10 年,而且很可能更长。不知怎的,我没有看到人们在这10年后,深钻研工作的加密代码,只是因为它可能是不安全的。人们只需要查看密码泄露的最后几个大“事故”,看看它的可能性有多大,这对于看到原始数据库的任何人来说都是显而易见的事情。

That being said: NSA crypto suite B allows only AES for symmetric encryption, of any kind. NIST list only SHA-1 and SHA-2 algorithms for HMAC and KDF use, while SHA-1 use is not recommended. Suite B allows onlySHA-2 hash functions. Those algorithms are publicly available, so why shouldn't I use them?

话虽如此:NSA 加密套件 B 只允许使用 AES 进行任何类型的对称加密。NIST 仅列出了 HMAC 和 KDF 使用的 SHA-1 和 SHA-2 算法,而不推荐使用 SHA-1 。Suite B允许使用SHA-2 哈希函数。这些算法是公开可用的,那么我为什么不应该使用它们呢?

回答by Hubert Kario

In the end I went with PKCS#8 files encrypted using PBEWithSHA256And256BitAES-CBC-BC

最后,我使用了加密的 PKCS#8 文件 PBEWithSHA256And256BitAES-CBC-BC

Encryption:

加密:

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;
    }
}

Decryption:

解密:

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;
        }
    }
}

回答by Neil Madden

Since Java 8, you can create a PKCS#12 keystore and pass an explicit PasswordProtectionparameter when storing a key to specify the encryption algorithm to use:

从 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);
    }
}

You can read a bit more on the details in this article(dislaimer: I wrote that article).

您可以在本文中阅读更多详细信息(免责声明:我写了那篇文章)。

回答by Maarten Bodewes

Triple DES is pretty strong, and Oracle likely uses keys with 168 bit of entropy (giving a full 112 bits of security at the time of writing).

三重 DES 非常强大,Oracle 可能使用具有 168 位熵的密钥(在撰写本文时提供完整的 112 位安全性)。

Furthermore, although MD5 maynot be secure for e.g. signatures, it is certainly valid for use in a key derivation scheme as such as PBE.

此外,尽管 MD5可能不安全,例如签名,但它肯定适用于诸如 PBE 之类的密钥派生方案。

Of course it would be a good idea for Oracle to migrate away from these schemes in time, but Triple DES and MD5 should not worry you needlessly. Writing your own is probably a worse idea, there are too many pitfalls.

当然,Oracle 及时从这些方案中迁移出去是个好主意,但是 Triple DES 和 MD5 不必让您担心。自己写可能是一个更糟糕的主意,有太多的陷阱。

Choose a good password, it's probably the best thing you can do. Or put your keystore in a correctly configured HSM or smartcard if you want high end security.

选择一个好的密码,这可能是您能做的最好的事情。如果您想要高端安全性,或者将您的密钥库放在正确配置的 HSM 或智能卡中。

回答by Ravi Tyagi

Main Class

主班

`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));
} }`

AEScryptography Class

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 Class

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;
}
 }`