Add pem-api module: PEMDecoder/PEMEncoder (JEP 538 preview on JDK 27, JEP 542 final in JDK 28) for JWT keys, TLS, encrypted keys and BouncyCastle migration
Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01CifR2BE7PBvRy5hT8TS9oz
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import java.security.*;
|
||||
import javax.crypto.EncryptedPrivateKeyInfo;
|
||||
import javax.crypto.spec.PBEParameterSpec;
|
||||
|
||||
/** Encrypted private keys: write one, read one, read one openssl wrote, and every way it can go wrong. */
|
||||
public class EncryptedKeys {
|
||||
static final char[] PW = "changeit".toCharArray();
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
PEMDecoder d = PEMDecoder.of();
|
||||
PEMEncoder e = PEMEncoder.of();
|
||||
PrivateKey key = d.decode(Fx.read("rsa-private-pkcs8.pem"), PrivateKey.class);
|
||||
|
||||
Fx.h("write an encrypted key with the defaults");
|
||||
String enc = e.withEncryption(PW).encodeToString(key);
|
||||
System.out.println(enc.lines().filter(l -> l.startsWith("-----")).toList());
|
||||
EncryptedPrivateKeyInfo info = (EncryptedPrivateKeyInfo) d.decode(enc);
|
||||
System.out.println("default algorithm: " + info.getAlgName());
|
||||
PBEParameterSpec spec = info.getAlgParameters().getParameterSpec(PBEParameterSpec.class);
|
||||
System.out.println("default iteration count: " + spec.getIterationCount() + ", salt bytes: " + spec.getSalt().length);
|
||||
|
||||
Fx.h("read it back");
|
||||
Fx.check(d.withDecryption(PW).decode(enc, PrivateKey.class).equals(key), "withDecryption(password) returns the original key");
|
||||
|
||||
Fx.h("choose the algorithm and cost yourself");
|
||||
PBEParameterSpec custom = new PBEParameterSpec(new SecureRandom().generateSeed(16), 600_000);
|
||||
EncryptedPrivateKeyInfo strong = EncryptedPrivateKeyInfo.encrypt(key, PW, "PBEWithHmacSHA512AndAES_256", custom, null);
|
||||
String strongPem = e.encodeToString(strong);
|
||||
EncryptedPrivateKeyInfo again = (EncryptedPrivateKeyInfo) d.decode(strongPem);
|
||||
System.out.println(again.getAlgName() + ", iterations " + again.getAlgParameters().getParameterSpec(PBEParameterSpec.class).getIterationCount());
|
||||
Fx.check(again.getKey(PW).equals(key), "getKey(password) on the decoded EncryptedPrivateKeyInfo works too");
|
||||
|
||||
Fx.h("a file written by openssl pkcs8 -topk8 -v2 aes-256-cbc");
|
||||
EncryptedPrivateKeyInfo fromOpenssl = (EncryptedPrivateKeyInfo) d.decode(Fx.read("rsa-private-encrypted.pem"));
|
||||
System.out.println(fromOpenssl.getAlgName());
|
||||
Fx.check(d.withDecryption(PW).decode(Fx.read("rsa-private-encrypted.pem"), PrivateKey.class).equals(key), "decrypts to the same key as the unencrypted file");
|
||||
|
||||
Fx.h("failures");
|
||||
String f = Fx.read("rsa-private-encrypted.pem");
|
||||
Fx.tryIt("no password, ask for PrivateKey", () -> d.decode(f, PrivateKey.class));
|
||||
Fx.tryIt("no password, no class", () -> d.decode(f));
|
||||
Fx.tryIt("wrong password", () -> d.withDecryption("nope".toCharArray()).decode(f, PrivateKey.class));
|
||||
Fx.tryIt("password on a key that is not encrypted", () -> d.withDecryption(PW).decode(Fx.read("rsa-private-pkcs8.pem"), PrivateKey.class));
|
||||
Fx.tryIt("openssl 'traditional' encrypted (Proc-Type: 4,ENCRYPTED)", () -> d.withDecryption(PW).decode(Fx.read("rsa-private-legacy-encrypted.pem"), PrivateKey.class));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user