Importing PEM encrypted key pair in java using bouncycastle - java

I'm writing a program that uses RSA for various tasks.
I know how to generate and write the key pair to file, but I cannot load the encrypted (AES-256-CFB) key pair to a KeyPair object.
So the question is: how do I load/decrypt an encrypted PEM key pair as a java.security.KeyPair object using the BouncyCastle library?
Thanks.
Generation/export code:
public void generateKeyPair(int keysize, File publicKeyFile, File privateKeyFile, String passphrase) throws FileNotFoundException, IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
SecureRandom random = new SecureRandom();
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "BC");
generator.initialize(keysize, random);
KeyPair pair = generator.generateKeyPair();
Key pubKey = pair.getPublic();
PEMWriter pubWriter = new PEMWriter(new FileWriter(publicKeyFile));
pubWriter.writeObject(pubKey);
pubWriter.close();
PEMWriter privWriter = new PEMWriter(new FileWriter(privateKeyFile));
if (passphrase == null) {
privWriter.writeObject(pair);
} else {
PEMEncryptor penc = (new JcePEMEncryptorBuilder("AES-256-CFB"))
.build(passphrase.toCharArray());
privWriter.writeObject(pair, penc);
}
privWriter.close();
}

I am assuming that you have set BouncyCastle as the security provider, for example with:
Security.addProvider(new BouncyCastleProvider());
The code you provided creates two key files, one for the private key and one for the public key. However, the public key is implicitly contained in the private key, so we only have to read the private key file to reconstruct the key pair.
The main steps then are:
Creating a PEMParser to read from the key file.
Create a JcePEMDecryptorProvider with the passphrase required to decrypt the key.
Create a JcaPEMKeyConverter to convert the decrypted key to a KeyPair.
KeyPair loadEncryptedKeyPair(File privateKeyFile, String passphrase)
throws FileNotFoundException, IOException {
FileReader reader = new FileReader(privateKeyFile);
PEMParser parser = new PEMParser(reader);
Object o = parser.readObject();
if (o == null) {
throw new IllegalArgumentException(
"Failed to read PEM object from file!");
}
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
if (o instanceof PEMKeyPair) {
PEMKeyPair keyPair = (PEMKeyPair)o;
return converter.getKeyPair(keyPair);
}
if (o instanceof PEMEncryptedKeyPair) {
PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair)o;
PEMDecryptorProvider decryptor =
new JcePEMDecryptorProviderBuilder().build(passphrase.toCharArray());
return converter.getKeyPair(encryptedKeyPair.decryptKeyPair(decryptor));
}
throw new IllegalArgumentException("Invalid object type: " + o.getClass());
}
Example usage:
File privKeyFile = new File("priv.pem");
String passphrase = "abc";
try {
KeyPair keyPair = loadEncryptedKeyPair(privKeyFile, passphrase);
} catch (IOException ex) {
System.err.println(ex);
}
Reference: BouncyCastle unit test for key parsing (link).

Related

Diffie-Hellman algorithm between C# and Java

I have console applications in c# and Java. Both of them generate public and private keys for Eliptic curved Diffie-Hellman algorithm. Public keys are encrypted in base64 and then are printed in console. Then I paste public key from c# into java program and vice verca. Unforutunately in the end derived keys which have to be the same are different. Seems like the configurations of algorithms are the same and there are no exceptions.
C#:
static void Main(string[] args)
{
ECDiffieHellmanCng eCDiffie = new ECDiffieHellmanCng(256);
eCDiffie.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
eCDiffie.HashAlgorithm = CngAlgorithm.Sha256;
byte[] myPublicKey = eCDiffie.ExportSubjectPublicKeyInfo(); //export in x509 format
String myPublicKeyBase64 = Convert.ToBase64String(myPublicKey);
Console.WriteLine(myPublicKeyBase64);
string otherKey = Console.ReadLine(); // here paste public key in console from Java
byte[] otherKeyFromBase64 = Convert.FromBase64String(otherKey);
ECDiffieHellmanCng eCDiffie2 = new ECDiffieHellmanCng(256);
eCDiffie2.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
eCDiffie2.HashAlgorithm = CngAlgorithm.Sha256;
int some = 0;
eCDiffie2.ImportSubjectPublicKeyInfo(otherKeyFromBase64, out some);
byte[] otherKeyDecoded = eCDiffie2.PublicKey.ToByteArray();
CngKey k = CngKey.Import(otherKeyDecoded, CngKeyBlobFormat.EccPublicBlob);
byte[] derivedKey = eCDiffie.DeriveKeyMaterial(k);
String derivedKeyBase64 = Convert.ToBase64String(derivedKey);
Console.WriteLine("Derived key: ");
Console.WriteLine(derivedKeyBase64);
}
Java:
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException {
// Generate ephemeral ECDH keypair
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(256);
KeyPair kp = kpg.generateKeyPair();
byte[] ourPk = kp.getPublic().getEncoded(); //public key in x509 format
// Display our public key
byte[] ourPublicKeyBase64 = Base64.getEncoder().encode(ourPk);
System.out.println(String.format("Public Key: %s", new String(ourPublicKeyBase64)));
// Read other's public key:
Scanner in = new Scanner(System.in);
String oth = in.nextLine(); // here paste in console public key C#
byte[] otherPk = Base64.getDecoder().decode(oth);
KeyFactory kf = KeyFactory.getInstance("EC");
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(otherPk);
PublicKey otherPublicKey = kf.generatePublic(pkSpec);
// Perform key agreement
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
ka.init(kp.getPrivate());
ka.doPhase(otherPublicKey, true);
// Read shared secret
byte[] sharedSecret = ka.generateSecret();
// Derive a key from the shared secret and both public keys
MessageDigest hash = MessageDigest.getInstance("SHA-256");
hash.update(sharedSecret);
// Simple deterministic ordering
List<ByteBuffer> keys = Arrays.asList(ByteBuffer.wrap(ourPk), ByteBuffer.wrap(otherPk));
Collections.sort(keys);
hash.update(keys.get(0));
hash.update(keys.get(1));
byte[] derivedKey = hash.digest();
byte[] derivedKeyBase64 = Base64.getEncoder().encode(derivedKey);
System.out.println(String.format("Derived key: %s", new String(derivedKeyBase64)));
}
Your Java code does some extra work to obtain derived key. Just remove following lines and you will get the same key as in ะก# code:
// Simple deterministic ordering
List<ByteBuffer> keys = Arrays.asList(ByteBuffer.wrap(ourPk), ByteBuffer.wrap(otherPk));
Collections.sort(keys);
hash.update(keys.get(0));
hash.update(keys.get(1));
Here is the whole Java code just for completeness:
public static void main(String[] args) throws Exception {
// Generate ephemeral ECDH keypair
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(256);
KeyPair kp = kpg.generateKeyPair();
byte[] ourPk = kp.getPublic().getEncoded(); //public key in x509 format
// Display our public key
byte[] ourPublicKeyBase64 = Base64.getEncoder().encode(ourPk);
System.out.println(String.format("Public Key: %s", new String(ourPublicKeyBase64)));
// Read other's public key:
Scanner in = new Scanner(System.in);
String oth = in.nextLine(); // here paste in console public key C#
byte[] otherPk = Base64.getDecoder().decode(oth);
KeyFactory kf = KeyFactory.getInstance("EC");
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(otherPk);
PublicKey otherPublicKey = kf.generatePublic(pkSpec);
// Perform key agreement
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
ka.init(kp.getPrivate());
ka.doPhase(otherPublicKey, true);
// Read shared secret
byte[] sharedSecret = ka.generateSecret();
// Derive a key from the shared secret
MessageDigest hash = MessageDigest.getInstance("SHA-256");
hash.update(sharedSecret);
byte[] derivedKey = hash.digest();
byte[] derivedKeyBase64 = Base64.getEncoder().encode(derivedKey);
System.out.println(String.format("Derived key: %s", new String(derivedKeyBase64)));
}

ECDSA byte array into Private Key Error

I want to save my private key in json file ( hex format ) then read it as PrivateKey.
Here Keys generate function
public void generateKeyPair() {
try {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA", "BC");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256k1");
keyGen.initialize(ecSpec,random);
KeyPair keyPair = keyGen.generateKeyPair();
privateKey = keyPair.getPrivate();
publicKey = keyPair.getPublic();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
This is how i save it into json file
a.generateKeyPair();
byte[] enc_key = a.privateKey.getEncoded();
StringBuilder key_builder = new StringBuilder();
for(byte b : enc_key){
key_builder.append(String.format( "%02X",b));
}
String serialized_key = key_builder.toString();
account.privateKey=serialized_key;
try (Writer writer = new FileWriter("Output.json")) {
Gson gson = new GsonBuilder().create();
gson.toJson(account, writer);
} catch (IOException e) {
e.printStackTrace();
}
And read it from file
Gson gson = new GsonBuilder().create();
try (Reader read1 = new FileReader("Output.json")) {
account=gson.fromJson(read1,account.getClass());
byte[] encoded_key=account.privateKey.getBytes();
a.privateKey = getPrivateKey(encoded_key);
public static PrivateKey getPrivateKey(byte[] privkey) throws NoSuchAlgorithmException, InvalidKeySpecException {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privkey);
KeyFactory kf = null;
try {
kf = KeyFactory.getInstance("ECDSA", "BC");
} catch (NoSuchProviderException e) {
e.printStackTrace();
}
PrivateKey privateKey = kf.generatePrivate(privateKeySpec);
return privateKey;
}
Function fail and i get error
java.security.spec.InvalidKeySpecException: encoded key spec not recognized: failed to construct sequence from byte[]: unknown tag 19 encountered
You are forgetting to hex decode the private key. Just performing getBytes won't do that.
The encoded byte starts with a SEQUENCE, tag 0x30. This in hex will of course be "30" or, in ASCII: 0x33, 0x30: these are the two first bytes returned by getBytes. Now the decoder looks at the first byte with bit value 0b001_10011. The last 5 bits encode the tag value, which is 16 + 2 + 1 = 19. Hence the specific error.

Java AES / GCM decryption fails

I am trying to use GCM Mode for encryption and decryption. Unfortunately decryption doesn't work.
Do I have to use the same initialization vector for both encryption and decryption classes? I already tried that, unsuccessfully...
Could the random argument in keyGen.init(128, random) be the problem?
Encryption code:
public class AES128SymmetricEncryption {
private static final int GCM_NONCE_LENGTH = 12; // in bytes
private static final int GCM_TAG_LENGTH = 16; // in bytes
public static void encode (FileInputStream ciphertextSource, FileOutputStream plaintextDestination)
{
try {
int numRead;
SecureRandom random = SecureRandom.getInstanceStrong();
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128, random);
SecretKey key = keyGen.generateKey();
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, getIV(random));
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
byte[] buf = new byte[2048];
while ((numRead = ciphertextSource.read(buf)) > 0) {
byte[] decryptedBlock = cipher.update(buf, 0, numRead);
plaintextDestination.write(decryptedBlock);
}
}
catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (plaintextDestination != null) {
ciphertextSource.close();
}
if (plaintextDestination != null) {
plaintextDestination.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static byte[] getIV(SecureRandom random) {
final byte[] nonce = new byte[GCM_NONCE_LENGTH];
random.nextBytes(nonce);
System.out.println(nonce);
return nonce;
}
public static void main(String[] args) throws GeneralSecurityException, IOException
{
Security.addProvider(new BouncyCastleProvider());
FileInputStream fis = new FileInputStream("C:/Users/roehrlef/Desktop/Test Data/Source Data/100KB.jpg");
FileOutputStream fos = new FileOutputStream("C:/Users/roehrlef/Desktop/Test Data/Encrypted Data/encrypted.jpg");
encode(fis, fos);
}
}
Decryption code:
public class AES128SymmetricDecryption {
private static final int GCM_NONCE_LENGTH = 12; // in bytes
private static final int GCM_TAG_LENGTH = 16; // in bytes
public static void decode (FileInputStream ciphertextSource, FileOutputStream plaintextDestination)
{
try {
int numRead = 0;
SecureRandom random = SecureRandom.getInstanceStrong();
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128, random);
SecretKey key = keyGen.generateKey();
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, getIV(random));
cipher.init(Cipher.DECRYPT_MODE, key, spec);
CipherInputStream cis = new CipherInputStream(ciphertextSource, cipher);
byte[] buf = new byte[2048];
while ((numRead = cis.read(buf)) > 0) {
byte[] decryptedBlock = cipher.update(buf, 0, numRead);
plaintextDestination.write(decryptedBlock);
}
}
catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (plaintextDestination != null) {
ciphertextSource.close();
}
if (plaintextDestination != null) {
plaintextDestination.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static byte[] getIV(SecureRandom random) {
final byte[] nonce = new byte[GCM_NONCE_LENGTH];
random.nextBytes(nonce);
System.out.println(nonce);
return nonce;
}
public static void main(String[] args) throws GeneralSecurityException, IOException
{
Security.addProvider(new BouncyCastleProvider());
FileInputStream fis = new FileInputStream("C:/Users/roehrlef/Desktop/Test Data/Encrypted Data/encrypted.jpg");
FileOutputStream fos = new FileOutputStream("C:/Users/roehrlef/Desktop/Test Data/Decrypted Data/decrypted.jpg");
decode(fis, fos);
}
}
You're using KeyGenerator twice; once for encryption and once for decryption. This class generates a new random key. With symmetric ciphers you need to use the same key for encryption and decryption (hence the name).
In general you should use the following classes for the following purposes:
For symmetric keys (e.g. AES, HMAC):
KeyGenerator: brand new secret (symmetric) keys;
SecretKeyFactory: decoding secret (symmetric) keys, for instance generated by the method Key#getEncoded() implemented by most key classes;
And for asymmetric public / private key pairs (e.g. RSA):
KeyPairGenerator: brand new public / private asymmetric key pairs;
KeyFactory: decoding public / private (asymmetric) keys from a stored key format, for instance generated by the method Key#getEncoded() implemented by most key classes;
Both symmetric and asymmetric keys may be stored in key stores:
KeyStore: storing keys / certificates in a key container such as PKCS#12 key stores;
Finally there are some other options for creating keys:
KeyAgreement: establishing a key by a key agreement function such as Diffie-Hellman key exchange;
Cipher#unwrap: unwrapping (decrypting) keys created using Cipher#wrap (or a similar function on another platform) with another key.
You should probably either store and retrieve the key in a KeyStore - which you can load / save to file. Note that not all key stores are created equal; Java 9 expanded the functionality of PKCS#12 key stores and made them the default. You code also encode the key and use a SecretKeyFactory to decode it again.
Or you can just cheat and reuse the SecretKey instance you generated during encryption, and implement key storage later. That would be good for testing purposes. In the end you need to share the key for symmetric encryption.
And yes, the IV needs to be identical on both sides. Usually it is just stored in front of the ciphertext. The IV should be unique for each encryption, so you have to use the random number generator over there.

Java decrypt email attachment (.p7m file)

I have an email attachment in .p7m format and a .pem file containing private keys and certificates.
Using OpenSSL I can decrypt the file with this command:
openssl smime -decrypt -inform DER -in fileToDecrypt.p7m -inkey privateKey.pem -out destinationFile
But using bouncycastle in Java, I could not decrypt it.
I read the private key with this code:
PEMReader pemReader = new PEMReader(new InputStreamReader(new FileInputStream(privateKeyName)));
Object obj;
PrivateKey key = null;
X509Certificate cert1 = null;
X509Certificate cert2 = null;
obj = pemReader.readObject();
if (obj instanceof PrivateKey) {
key = (PrivateKey) obj;
System.out.println("Private Key found");
}
obj = pemReader.readObject();
if(obj instanceof X509Certificate){
cert1 = (X509Certificate) obj;
System.out.println("cert found");
}
obj = pemReader.readObject();
if(obj instanceof X509Certificate){
cert2 = (X509Certificate) obj;
System.out.println("cert found");
}
This prints out:
Private Key Found
cert found
cert found
The type of the keys are:
System.out.println(key.getAlgorithm());
System.out.println(cert1.getSigAlgName());
System.out.println(cert2.getSigAlgName());
RSA
SHA256WithRSAEncryption
SHA256WithRSAEncryption
If I try to decrypt like this:
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, key);
Path path = Paths.get("fileToDecrypt.p7m");
byte[] data = Files.readAllBytes(path);
byte[] decryptedData = cipher.doFinal(data);
I get:
javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes
I have this two files:
fileToDecrypt.p7m
privateKey.pem: containing RSA private key and two X508 Certificates
And I don't know where to begin what to decrypt with what, and how?
Solution to the problem:
private static byte[] cmsDecrypt(byte[] message, PrivateKey key) throws
Exception {
CMSEnvelopedDataParser ep = new CMSEnvelopedDataParser(message);
RecipientInformationStore recipients = ep.getRecipientInfos();
Collection c = recipients.getRecipients();
Iterator iter = c.iterator();
RecipientInformation recipient = (RecipientInformation) iter.next();
return recipient.getContent(key, new BouncyCastleProvider());
}
Path path = Paths.get("fileToDecrypt.p7m");
byte[] data = Files.readAllBytes(path);
try {
System.out.println(new String(cmsDecrypt(data, key)));
} catch (Exception e) {
e.printStackTrace();
}

RSA - bouncycastle PEMReader returning PEMKeyPair instead of AsymmetricCipherKeyPair for reading private key

I have a function that successfully reads a openssl formatted private key:
static AsymmetricKeyParameter readPrivateKey(string privateKeyFileName)
{
AsymmetricCipherKeyPair keyPair;
using (var reader = File.OpenText(privateKeyFileName))
keyPair = (AsymmetricCipherKeyPair)new PemReader(reader).ReadObject();
return keyPair.Private;
}
and returns an AsymmetricKeyParameter which is then used to decrypt an encrypted text.
Below is the decrypt code:
public static byte[] Decrypt3(byte[] data, string pemFilename)
{
string result = "";
try {
AsymmetricKeyParameter key = readPrivateKey(pemFilename);
RsaEngine e = new RsaEngine();
e.Init(false, key);
//byte[] cipheredBytes = GetBytes(encryptedMsg);
//Debug.Log (encryptedMsg);
byte[] cipheredBytes = e.ProcessBlock(data, 0, data.Length);
//result = Encoding.UTF8.GetString(cipheredBytes);
//return result;
return cipheredBytes;
} catch (Exception e) {
Debug.Log ("Exception in Decrypt3: " + e.Message);
return GetBytes(e.Message);
}
}
These work in C# using bouncy castle library and I get the correct decrypted text. However, when I added this to Java, the PEMParser.readObject() returns an object of type PEMKeyPair instead of AsymmetricCipherKeyPair and java throws an exception trying to cast it. I checked in C# and it is actually returning AsymmetricCipherKeyPair.
I don't know why Java behaves differently but I hope someone here can help how to cast this object or read the privatekey file and decrypt successfully. I used the same public and privatekey files in both C# and Java code so I don't think the error is from them.
Here for reference the Java version of how I'm reading the privatekey:
public static String readPrivateKey3(String pemFilename) throws FileNotFoundException, IOException
{
AsymmetricCipherKeyPair keyParam = null;
AsymmetricKeyParameter keyPair = null;
PEMKeyPair kp = null;
//PrivateKeyInfo pi = null;
try {
//var fileStream = System.IO.File.OpenText(pemFilename);
String absolutePath = "";
absolutePath = Encryption.class.getProtectionDomain().getCodeSource().getLocation().getPath();
absolutePath = absolutePath.substring(0, (absolutePath.lastIndexOf("/")+1));
String filePath = "";
filePath = absolutePath + pemFilename;
File f = new File(filePath);
//return filePath;
FileReader fileReader = new FileReader(f);
PEMParser r = new PEMParser(fileReader);
keyParam = (AsymmetricCipherKeyPair) r.readObject();
return keyParam.toString();
}
catch (Exception e) {
return "hello: " + e.getMessage() + e.getLocalizedMessage() + e.toString();
//return e.toString();
//return pi;
}
}
The Java code has been updated to a new API, which is yet to be ported across to C#. You could try the equivalent (but now deprecated) Java PEMReader class. It will return a JCE KeyPair though (part of the reason for the change was because the original version worked only with JCE types, not BC lightweight classes).
If using PEMParser, and you get back a PEMKeyPair, you can use org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter.getKeyPair to get a JCE KeyPair from it. Ideally there would be a BCPEMKeyConverter, but it doesn't appear to have been written yet. In any case, it should be easy to make an AsymmetricCipherKeyPair:
PEMKeyPair kp = ...;
AsymmetricKeyParameter privKey = PrivateKeyFactory.createKey(kp.getPrivateKeyInfo());
AsymmetricKeyParameter pubKey = PublicKeyFactory.createKey(kp.getPublicKeyInfo());
new AsymmetricCipherKeyPair(pubKey, privKey);
Those factory classes are in the org.bouncycastle.crypto.util package.

Categories

Resources