Java 21: Generate encrypted private RSA key in PEM format

I’d like to generate an RSA key pair in Java 21 using plain Java API (without using BouncyCastle or similar libraries). The private key should be processable by both OpenSSL and Java. Based on Java limitations, this means that I’d like to generate an AES-encrypted private key.

The code below works fine for generating unencrypted private keys, but despite many Google & StackOverflow searches, I haven’t been able to successfully implement the pemEncrypt method. I’m getting somewhat lost in the many algorithms and formats; maybe I need to do extra work for generating a proper ASN.1 data structure?

   @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
    public static final class KPGenerator {
        private final char[] passPhrase;
        
        @SneakyThrows
        public final KeyPair generate() {
            KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
            kpg.initialize(2048); 
            return kpg.generateKeyPair();
        }
        
        @SneakyThrows
        public final void writePem(Path privateKeyPath, Path publicKeyPath) {
            var kp = generate();
            writePem("PRIVATE KEY", kp.getPrivate().getEncoded(), privateKeyPath);
            writePem("PUBLIC KEY", kp.getPublic().getEncoded(), publicKeyPath);
        }
        
        @SneakyThrows
        private final void writePem(String type, byte[] key, Path path) {
            var pemString = asPem(type, key);
            Files.writeString(path, pemString, StandardOpenOption.CREATE_NEW);
        }
        
        private final String asPem(String type, byte[] key) {
            if ( "PRIVATE KEY".equals(type) && passPhrase!=null ) {
                return asPem("ENCRYPTED PRIVATE KEY", pemEncrypt(key, passPhrase));
            }
            return "-----BEGIN "+type+"-----n"
                    + Base64.getMimeEncoder().encodeToString(key)
                    + "n-----END "+type+"-----";
        }
        
        @SneakyThrows
        private static final byte[] pemEncrypt(byte[] privateKey, char[] passPhrase) {
           ???
        }
}

Below are some examples of pemEncrypt implementations that I’ve tried.

Method 1

Based on https://medium.com/@patc888/decrypt-openssl-encrypted-data-in-java-4c31983afe19

Problem: openssl asn1parse -in privatekey.pem throws an error: 4047F272597F0000:error:0680009B:asn1 encoding routines:ASN1_get_object:too long:../crypto/asn1/asn1_lib.c:95

        @SneakyThrows
        private static final byte[] pemEncrypt(byte[] privateKey, char[] passPhrase) {
            var salt = createSalt();
            byte[] passAndSalt = ArrayUtils.addAll(toBytes(passPhrase), salt);
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] key = md.digest(passAndSalt);
            SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
            md.reset();
            byte[] iv = Arrays.copyOfRange(md.digest(ArrayUtils.addAll(key, passAndSalt)), 0, 16);
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
            byte[] encryptedSecretKey = cipher.doFinal(privateKey);
            try ( var bos = new ByteArrayOutputStream(); ) {
                bos.writeBytes("Salted__".getBytes(StandardCharsets.US_ASCII));
                bos.writeBytes(salt);
                bos.writeBytes(encryptedSecretKey);
                return bos.toByteArray();
            }
        }
        private static final byte[] toBytes(char[] chars) {
            CharBuffer charBuffer = CharBuffer.wrap(chars);
            ByteBuffer byteBuffer = Charset.forName("UTF-8").encode(charBuffer);
            byte[] bytes = Arrays.copyOfRange(byteBuffer.array(),
                      byteBuffer.position(), byteBuffer.limit());
            Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
            return bytes;
        }
        private static final byte[] createSalt() {
            final byte[] salt = new byte[16];
            new SecureRandom().nextBytes(salt);
            return salt;
        }

Sample output:

-----BEGIN ENCRYPTED PRIVATE KEY-----
U2FsdGVkX193omAgeegjjYcN8h3riBDrFrFVpOSxkaEIo1sFg9ZT89RkN+fCBYpzFROB4UOmfBVs
0t/3mfLIID8eDjRH59KU9BCXwXt9unpYGWjFL4Vv5OQsRefiVM57FpbQ5YS6xPhfq0qQT39uTI7p
wG9XSHEBogoYiHTSx3+IIj5/iPNWuE3rMxRJUX/tERFfl6SSY1SlWR3zzy2LwlCddklzIqyZkLSk
cUE+yHTFsFnLuWdkaVMVrWY/isArukuc/gAWA9LThdF+il87s1D6tVtZOpFAmhqYUXhX6Si4scDK
910xEhqZhxqsybBih51+y0GqvcxgVpkadoEaz2WAcAq9aOykjvWGEhtGrCJ9JqhiiIVnY7bcHZxv
cPAAGAOoP95BHdDFR0VYnX1I+OzRxl0XDTVQEsYIWFV0t3u5/dUy/x2yOgkhFer7feAiY8yo7fnU
E1YOsSW2PnF8E2pXBeiJoemw5BimSfuqbuYr140gyDvPvjcfQBwr4vUuKXRxPnTwzjzvqYY5vNXG
p3+TTTL++DMOX/Us0kRVHERdsznn/E20QqBie4f2xa06Vkpf8SZMyO+aSQXRJeVeGH1LtaBLPX9R
hflr6SaX94E9Du91r9Qif1dI451P4M0+Zo3oQTmfy6RwKwckiP+L7LRyfZKLdRk2OTMcP2PX20DZ
SpeYzEzY6LxsaGAiQnHQ+jX9173455PJ1YCRANfCVCjVFr4enAIHqbxId8XD2HdEp3wB76Bj/uFF
NPIzA/gY6ezrUeikGrPYdbgXZ0meKmEJkSxl32ojCmDmhGShxVXRI8c8Subdj+mFS6/BPCwQ2W5V
UeS3fbblUbtpii0XdJoeh+ek/+AGwnkVI7BFse1sZqG4KRr/beH4Z7P2oms1a9ZWBihVveI8xRL1
HcLVi/lgrD25Rw9c+BVDlpCNpr9C42AJgD8XiLQsfd3kj+S5nOTjf61+onz7VQR3LtFDSex2biBL
4DlAbqQzFVM9F2LO0BZeaCLcVKsQ1vmmLYahvklhAcMBco8rZ/8aw0dzVt6L5pWNytH/fYRBRbSI
F1rjnEWmGjF+aXFbjg/SMAV+YSNg03OM/BrU9HpEXH40PB7sJrbY4nqtX/4igwDkMjEVsz0ThZt/
BODzNhxND+HxcXIKjnr5W+3FKUmtkhAMHYgWShzmYCUlt4rdUWjZSL9I6YW0TpG69NBpSw0qihCD
31iUL3uDLe+1QiCoa3/MvPo5CrXheHwV2RNGwIaZRYS6Fzz5VG5j4iI2r8eB8nysiZpTC9hgrlXj
1HdZiKqQDAm3N27eDMLDOfvtJz1x8GfMI+agoj7KB9W48rMNtk0+Vmv5THf6EkeM+KhEWjff4Ju7
cShPd9tJKDwDHPKSUQyCZegIHJfEZeS5gZ6TPeE/L6mEvHBmW3YQpsDkI7lBo/qQYe7UyrAHVTIY
sYNdIA74LvrXSPHlIumjT4lPl9aTc7vaoLfrJc1EnTatujBqrVucdB0LdFWQ34s9YdLICZUV2X25
xUfIpIWZ6G6S8THqGICZ61tAXvRGRwVT2JlsiG6967G84fda0CpT/huJEiTrZRlETfAOEyfQJFJp
shxD+6gTYaIwMSmx2Tq7KyC9emddILIFu1wZgY2H6096n/MM9RuLIxgWOT9MscYWNzlGF+L9XSJH
98c=

Method 2

Problem: Similar OpenSSL error as before: 40B70A48E47F0000:error:0680007B:asn1 encoding routines:ASN1_get_object:header too long:../crypto/asn1/asn1_lib.c:105:

        @SneakyThrows
        private static final byte[] pemEncrypt(byte[] key, char[] passPhrase) {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, createKey(passPhrase), createIv());
            return cipher.doFinal(key);
        }
        @SneakyThrows
        private static final SecretKey createKey(char[] passPhrase) {
            //PBE results in invalid key length exception
            //SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_256");
            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            KeySpec spec = new PBEKeySpec(passPhrase, createSalt(), 65536, 256);
            SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
            return secret;
        }
        
        private static final IvParameterSpec createIv() {
            final byte[] iv = new byte[16];
            new SecureRandom().nextBytes(iv);
            return new IvParameterSpec(iv);
        }
        
        private static final byte[] createSalt() {
            final byte[] salt = new byte[16];
            new SecureRandom().nextBytes(salt);
            return salt;
        }

Sample output:

-----BEGIN ENCRYPTED PRIVATE KEY-----
iBGmburj5hml/segy4KnBXwIm1kYGADhB0KAhfgB1yFsyyoRXC5BlwT6Dq4OYHROtkhsttwV1+5V
cqCarwq1WJmBtWlnohh3oU4YRAkmHj6nMh8fFCRutaySBuN75bqI9tSJ0+1nRsg+saU2F1rGxHma
WhcHB3KZqZe8sNlsTBvDzR6NYU1+XNbxskbOiniA6eJAXGuYpfVtoLlY3BX3AtbK8d+e2XBMF0SA
ddMhUtZblIAsj36fnxmGIql/sHfCLEsOUBDK8fdpA1YryLpg5HhE4Fwht++hamtZUgvW0vNw1q7e
PrhSGY5pOgIdtb1EPdswnHNd77C2/qDC/it3on9mU6U9lvGbP7TyqO0vkXWdDY8hzwowNauy6pTv
YneTQCX1rLwTkuMJMq9ObvoPd+Z0ojmS25eSFlAxIin1DZ3wUKcox2u0DIVA5iF5M4wQRn3GiIJU
tjX4B45t935gxPlNMB7EGRySBCidCmWngTWlUF4KhRD6bcf/BlD70LLRxD37SBT/kpExhaewvUx8
Obrj5hXEHxhyf7h28Rb/LXj5C5MwbG3mAlWWlk1cLW6vezvVi2xpVsjIuoCVmN2iv3RklQtL018I
yUEEUeJZObfdZ13c9ha/Pf4a/+mIx6HJUkQzsH0BxkXifxSYBw7/CtH+0nRZRRswo/AbxTVDEVyV
A0UcWfCZa9PW52mWqZPp7aggI7Zy/IjMYQ6q4YMLWQbzCPGl3aQZ/b7VFO7kYrPsyZwyj9IMQIWt
AUI2gvtz5DysTekrMU0aiU/BS1rrl6i9C7FnmZHEBCf6DjtgKl3f0ZwPo5r2UejvdV4rw4sFqrR4
DYdva5McjmFRpiqpvDNXnRUliLXqXorsJgTCga+kX4YfHDhjjAdU+WvFMnC14FqbiJ6bb6dnGZ93
AdTPdOFV3tAX/M9mEMGqcVKazkVSzC0ZV7bxYO0IKpKpufRganb9emHu+A7EZPiBZKm7/IEnI8vq
NISt8raICwPfrl7lkg+VLfYC/8ubhpMY8itsDi/LHzOcAj4Tw0Hyayymy7chUSPfKJu4homVbeMY
vvSpBVJwUMa4rs+NQH6ocpUk3o/OuEdbY29KceqH8SQQtshZ7NQN6NTDJl40l3A7jdDK62PEJksc
BeHrKRiT3wMLKnhbEHbyXeL47j17Bbq0SWzUQuS7pBasIf+UsiQVKNLxYepIJdneRCsqW32zu7n3
DOx/ZftThUCU7NjmbVgWOmj0NVAEa5zfkwMKuqNyDRbTTkiw4tSxnbuqQGOZOp3G0e3pJ+kPui+p
qLsIyCY9l/wk2zy1WafD6ddP7jtvVB89SFVMN61DiPmJVQoq6hrFLHlv+lQE970sWGsmZ3b7BxBw
QLA0c3sKF9DEFIpv9B/VU6YtyYjTi81hmWZ/MuzEhj6ZW5VNqgUAAODb5Tgk8cLELYrxgoWyxMuX
yF6WZ7XIdFx5TKYstzhsDSBkpfaQxqzSmg8OvVQL9FLCjs+CoBjTUxzixHz5OSysapf4aMiWZS3t
UfNfYoJFjRCIvZBP1T9Q12kdLxM4kvcC6Ony4ZmIWLBYCOE7aBWW3+0AGH4NO2eQkJDTIvOQkC1n
YSL/k9O3eYi+eZ4d21ZcWJRmOMjftR3qVUSG8YzyNXAIBps=
-----END ENCRYPTED PRIVATE KEY-----

Method 3

Based on ChatGPT.

Problem: Java exception java.security.InvalidKeyException: Wrong algorithm: AES or Rijndael required on Cipher.init()

        private static final byte[] pemEncrypt(byte[] privateKey, char[] passPhrase) {
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey);
            byte[] salt = createSalt();
            int iterationCount = 65536; // You can adjust this value
            int keyLength = 128; // You can adjust this value
    
            PBEKeySpec pbeKeySpec = new PBEKeySpec(passPhrase, salt, iterationCount, keyLength);
            SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
            SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);
    
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            byte[] encryptedKey = cipher.doFinal(pkcs8EncodedKeySpec.getEncoded());
    
            EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(cipher.getParameters(), encryptedKey);
            return encryptedPrivateKeyInfo.getEncoded();
        }

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật