Diffie-Hellman algorithm between C# and Java - 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)));
}

Related

Derive Shared Secret From ECDH with existing foreign public key

Im converting a method from nodeSJ to Java, but im having trouble getting it to work. Im stuck trying to calculate a derived shared secret.
Hoping someone can catch what im doing wrong porting over the nodeJS to Java.
NodeJS code:
//the public_key param here is from a different device.
sign: function(public_key)
{
//dummy values
var PRE_SALT_VALUE = 'f0f0f0f0f0';
var POST_SALT_VALUE = '0101010101';
const crypto = require('crypto');
var sha512 = crypto.createHash("sha512");
var EC = require('elliptic').ec;
var ec = new EC('p256');
// Generate keys
var key1 = ec.genKeyPair(); //key1 is gen before pub key
var key2 = ec.keyFromPublic(public_key, 'hex') //pub key gen from saved cert
var derived_secret = key1.derive(key2.getPublic());
var derived_secret = Buffer.from(derived_secret.toString(16), 'hex')
var public_key_client = key1.getPublic('hex')
var pre_salt = Buffer.from(PRE_SALT_VALUE, 'hex')
var post_salt = Buffer.from(POST_SALT_VALUE, 'hex')
derived_secret = Buffer.from(pre_salt.toString('hex')+derived_secret.toString('hex')+post_salt.toString('hex'), 'hex') // finalyze shared secret
// Hash shared secret
var sha = sha512.update(derived_secret);
derived_secret = sha.digest();
return {
public_key: public_key_client.toString('hex').slice(2), //anyone know why we drop the first byte here?
secret: derived_secret.toString('hex')
}
}
In progress Java Code:
//load cert from pem string (passed in from file), foreign cert
ByteArrayInputStream input = new ByteArrayInputStream(pem);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(input);
X509Certificate x509Cert = (X509Certificate) cert;
// get pub key from cert
PublicKey publicKeyForSignature = x509Cert.getPublicKey();
// Generate ephemeral ECDH keypair KEY1
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(256);
KeyPair kp1 = kpg.generateKeyPair();
byte[] ourPk = kp1.getPublic().getEncoded(); //use this later
//load KEY2 from others public key
KeyFactory kf = KeyFactory.getInstance("EC");
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(publicKeyForSignature.getEncoded());
PublicKey otherPublicKey = kf.generatePublic(pkSpec);
// Perform key agreement
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
ka.init(kp1.getPrivate());
ka.doPhase(otherPublicKey, true);
// Read shared secret
byte[] sharedSecret = ka.generateSecret();
// Derive a key from the shared secret and both salt keys
MessageDigest hash = MessageDigest.getInstance("SHA-512");
hash.update(Util.PRE_SALT_VALUE);
hash.update(sharedSecret);
hash.update(Util.POST_SALT_VALUE);
byte[] derivedKey = hash.digest();
... etc, derivedKey = secret returned in JS method, ourPk = public_key returned in JS method.
One thing i notice is that the public/private keys generated from nodejs vs java are different sizes? 65 bytes in node an 91 bytes in java. No idea why that would happen.
What stands out as wrong here?
Thanx
Edit:
So basically, I just need to know how to do this in Java
var EC = require('elliptic').ec;
var ec = new EC('p256');
// Generate keys
var key1 = ec.genKeyPair();
var key2 = ec.keyFromPublic(public_key, 'hex') //pub key from foreign device
var derived_secret = key1.derive(key2.getPublic());
Like already mentioned in the comments, to be able to use the shared secret between Java and Node you need to convert the keys accordingly.
For the key conversion code from this two fine stackoverflow answers can be used:
https://stackoverflow.com/a/57209308/2331445
https://stackoverflow.com/a/36033552
Test
To get a complete test case, one can write a Java program that generates a DER public key, converts it into an uncompressed EC key (65 bytes) and outputs it to the console. It then reads the other PK from the console, converts it to a public key, and prints out the shared secret.
The Node code uses the PK from the Java program, determines the shared secret and a public key. This public key can then be passed to the Java program via copy/paste, which is still waiting for the input.
The Java program finally determines the shared secret and prints it out.
If both shared secrets have the same value, we know it works.
Java
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.ECPointUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import javax.crypto.KeyAgreement;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.security.*;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
public class Main {
public static byte[] ecKeyBytesFromDERKey(byte[] ourPk) {
ASN1Sequence sequence = DERSequence.getInstance(ourPk);
DERBitString subjectPublicKey = (DERBitString) sequence.getObjectAt(1);
return subjectPublicKey.getBytes();
}
private static PublicKey publicKeyFromEC(byte[] ecKeyByteArray) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory kf = KeyFactory.getInstance("EC", "BC");
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256r1");
ECNamedCurveSpec params = new ECNamedCurveSpec("secp256r1", spec.getCurve(), spec.getG(), spec.getN());
ECPoint publicPoint = ECPointUtil.decodePoint(params.getCurve(), ecKeyByteArray);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(publicPoint, params);
return kf.generatePublic(pubKeySpec);
}
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
kpg.initialize(256);
KeyPair kp = kpg.generateKeyPair();
byte[] ourPk = kp.getPublic().getEncoded();
byte[] ecPublicKey = ecKeyBytesFromDERKey(ourPk);
System.out.println("our ec public key (65 bytes): " + Hex.encodeHexString(ecPublicKey));
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("other public key (65 bytes): ");
String input = br.readLine();
br.close();
byte[] otherPk = Hex.decodeHex(input);
PublicKey otherPublicKey = publicKeyFromEC(otherPk);
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
ka.init(kp.getPrivate());
ka.doPhase(otherPublicKey, true);
byte[] sharedSecret = ka.generateSecret();
System.out.println("Shared secret: " + Hex.encodeHexString(sharedSecret));
}
}
Node
One change is required in your node program:
In the line
public_key: public_key_client.toString('hex').slice(2), //anyone know why we drop the first byte here?
the .slice(2) needs to be removed:
public_key: public_key_client.toString('hex'),
because it removes the first byte (which is hex 04) needed to indicate that it is an uncompressed key.
So just using the public key from the Java program (which will be different for each run) the Node part could look like this:
var publickey = Buffer.from("<public key from java>", 'hex');
var derived = sign(publickey);
console.log(derived);
Test
In the upper area you see the Java program and in the lower area the output of the Node program. The shared secret is the same.

RSA Encrypting in c# and Decrypting data in java

I'm trying to encrypt data in my C# client with public RSA key from the Java server. To do this I generated KeyPair in Java
KeyPairGenerator.java
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair kp = kpg.generateKeyPair();
Key pub = kp.getPublic();
Key pvt = kp.getPrivate();
String outFile = "rsa_key";
FileOutputStream outPvt = new FileOutputStream(outFile + ".key");
outPvt.write(pvt.getEncoded());
outPvt.close();
FileOutputStream outPub = new FileOutputStream(outFile + ".pub");
outPub.write(pub.getEncoded());
outPub.close();
It gave me two files (rsa_key.key and rsa_key.pub) and then I encoded public key with Base64 so C# could read it:
PublicKeyBase64.java
String keyFile2 = "rsa_key.pub";
Path path2 = Paths.get(keyFile2);
byte[] bytes2 = Files.readAllBytes(path2);
X509EncodedKeySpec ks2 = new X509EncodedKeySpec(bytes2);
KeyFactory kf2 = KeyFactory.getInstance("RSA");
PublicKey pub = kf2.generatePublic(ks2);
Base64.Encoder encoder = Base64.getEncoder();
String outFile = "en_rsa_key";
Writer out = new FileWriter(outFile + ".pub");
out.write(encoder.encodeToString(pub.getEncoded()));
out.close();
Then I created my C# class to encrypt data
Encrypter.cs
class Encrypter
{
private const string PATH = "..\\..\\key\\en_rsa_key.pub";
private string PublicKey;
public Encrypter()
{
PublicKey = File.ReadAllText(PATH);
Console.WriteLine(PublicKey.Length);
}
public string encryptData(string dataToEncrypt)
{
Asn1Object obj = Asn1Object.FromByteArray(Convert.FromBase64String(PublicKey));
DerSequence publicKeySequence = (DerSequence)obj;
DerBitString encodedPublicKey = (DerBitString)publicKeySequence[1];
DerSequence publicKey = (DerSequence)Asn1Object.FromByteArray(encodedPublicKey.GetBytes());
DerInteger modulus = (DerInteger)publicKey[0];
DerInteger exponent = (DerInteger)publicKey[1];
RsaKeyParameters keyParameters = new RsaKeyParameters(false, modulus.PositiveValue, exponent.PositiveValue);
RSAParameters parameters = DotNetUtilities.ToRSAParameters(keyParameters);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(parameters);
//Console.WriteLine(dataToEncrypt);
byte[] encryptedData = rsa.Encrypt(Encoding.UTF8.GetBytes(dataToEncrypt), true);
//Console.WriteLine(Convert.ToBase64String(encryptedData));
return Convert.ToBase64String(encryptedData);
}
}
And at last my Decrypter class
public class Decrypter {
private static final String PATH = "I:\\rsa_key.key";
private File privateKeyFile = new File(PATH);
private RSAPrivateKey privateKey;
public Decrypter() throws Exception {
DataInputStream dis = new DataInputStream(new FileInputStream(privateKeyFile));
byte[] privateKeyBytes = new byte[(int)privateKeyFile.length()];
dis.read(privateKeyBytes);
dis.close();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(privateKeyBytes);
privateKey = (RSAPrivateKey) keyFactory.generatePrivate(privSpec);
}
public String decrypt(String data) throws Exception{
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return new String(cipher.doFinal(data.getBytes()));
}
}
I send encrypted data in C# via WebSocket and receive it without any problem because data while sending and receiving is the same. The problem is that data length is 344 characters (bytes) so when I decrypt my data it shows me and error: javax.crypto.IllegalBlockSizeException: Data must not be longer than 245 bytes
I'm using 2048 bit key so it's 256B - 11B for padding that's why it's 245B. But the problem is that it always generates 344B and it doesn't depend on message to encode length. For example encoding "A" gives 344B and "Hello world" also 344B.
In this topic people say to use symmetric key, Encrypt the data with the symmetric key and then Encrypt the symmetric key with rsa. But it won't help because any encrypted string in my code has 344B size, so I won't be able to decrypt encrypted symmetric key.
What's wrong with my code?

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.

Obtaining Secret Key from cert file to use in AES algorithm for encryption and decryption

I am doing a AES encryption , in which i will use a secret key from cert file as below to initialise the cipher.
encryptModeCipher = Cipher.getInstance("AES");
encryptModeCipher.init(Cipher.ENCRYPT_MODE, aesSecretKey);
But the problem i see here is that, my secretKey () remains the same for all the certificates that i use. Any suuggestion why? and suggest a good idea to do so.
byte[] encryptionKey = Arrays.copyOf(encoded, 32);
secretKey = new SecretKeySpec(encryptionKey, algorithm);
public class AESEncryptionServiceHelper {
private String algorithm = "AES";
private String certPass;
private SecretKey secretKey;
public SecretKey setKey() {
try {
certPass="****";
char[] pass = certPass.toCharArray();
KeyStore keyStore = KeyStore.getInstance("jceks");
File file = new File("D:/aws-kms-dps/***.jks");
InputStream inputStream = new FileInputStream(file);
keyStore.load(inputStream, pass);
Certificate cert = keyStore.getCertificate("****");
Key key = cert.getPublicKey();
secretKey = new SecretKeySpec(key.getEncoded(), algorithm);
byte[] encoded = secretKey.getEncoded();
byte[] encryptionKey = Arrays.copyOf(encoded, 32);
secretKey = new SecretKeySpec(encryptionKey, algorithm);
} catch (IOException e) {
System.out.println(e);
} catch (Exception e) {
System.out.println(e);
}
return secretKey;
}
public static void main(String args[]){
AESEncryptionServiceHelper aesEncryptionServiceHelper=new AESEncryptionServiceHelper();
aesEncryptionServiceHelper.setKey();
}
}
You seems you are usging (part of) the public key as an AES key. That is VERY BAD idea as
the public key is .. well .. public and static
it has relatively low entropy (as multiple bytes are defined in the ASN.1 format)
Did you do any research how to properly do encryption using PKI or you are just guessing / plaing with the crypto API?
Let's assume you want to do encryption using the public key and AES (it is called hybrid encryption), you could take example from my blog
Please read it and understand (or any other good blogs about cryptography), seems you are missing using IV (salt) and MAC
// generate random AES key
KeyGenerator keyGenerator = KeyGenerator.getInstance(SYMMETRIC_KEY_ALG);
SecretKey symmetricKey = keyGenerator.generateKey();
// this assumes there's whole keypair (including private key)
// normally only a certificate with PubKey is available
PublicKey pubKey = keystoreEntry.getCertificate().getPublicKey();
params.setKey(symmetricKey.getEncoded());
// execute symmetric encryption
this.symmetricEncryption(params);
// encrypt the key with the public key
Cipher cipher = Cipher.getInstance(PKI_CIPHER_ALG);
cipher.init(Cipher.WRAP_MODE, pubKey);
byte[] wrappedKey = cipher.wrap(symmetricKey);
LOGGER.log(Level.INFO, "Wrapped key: {0}", Base64.getEncoder().encodeToString(wrappedKey));
params.setKey(wrappedKey);
where the symetric encryption itself can be implemented as follows
// initialization vector
SecureRandom rnd = new SecureRandom();
byte[] iv = new byte[SYMMETRIC_BLOCK_SIZE / 8];
rnd.nextBytes(iv);
encryptionParams.setIv(iv);
IvParameterSpec ivParamSpec = new IvParameterSpec(iv);
SecretKey symmetricKey = new SecretKeySpec(encryptionParams.getKey(), SYMMETRIC_KEY_ALG);
Cipher cipher = Cipher.getInstance(SYMMETRIC_CIPHER_NAME);
cipher.init(Cipher.ENCRYPT_MODE, symmetricKey, ivParamSpec);
// for HMAC we should be able to use the same key as for encryption
// for CBC-MAC it may not be the case
// https://en.wikipedia.org/wiki/CBC-MAC#Using_the_same_key_for_encryption_and_authentication
Mac mac = Mac.getInstance(EncryptionTest.HASH_ALGORITHM_NAME);
mac.init(symmetricKey);
byte[] encrypted = cipher.doFinal(encryptionParams.getPlaintext());
encryptionParams.setCiphertext(encrypted);
byte[] authTag = mac.doFinal(encrypted);
encryptionParams.setMac(authTag);

Java - Encrypt String with existing public key file

I've been researching this for the past 4-5 hours now and can't seem to find an answer that actually works despite finding 'answers' that used everything from a few methods to an entire ~100 line class. I can't imagine that there isn't some simple function to do such a trivial thing :P
I have a pre-existing set of public / private keys (actually, two sets - one generated by ssh-keygen and another by openssl so .. whatever format works is cool).
All I am after is a simple java equivalent to what I write in python like -
key_object = someModule.KeyObject(nameOfPublicKeyFile)
def encrypt (SomePlainText) :
return someOtherModule.encrypt(key_object, SomePlainText)
Any help would be awesome!
These openssl commands in the shell create an RSA key pair and write the public and private keys to DER formatted files.
Here, the private key file is not password-protected (-nocrypt) to keep things simple.
$ openssl genrsa -out keypair.pem 2048
Generating RSA private key, 2048 bit long modulus
............+++
................................+++
e is 65537 (0x10001)
$ openssl rsa -in keypair.pem -outform DER -pubout -out public.der
writing RSA key
$ openssl pkcs8 -topk8 -nocrypt -in keypair.pem -outform DER -out private.der
Now that you have the DER files, you can read them in Java and use KeySpec and KeyFactory to create PublicKey and PrivateKey objects.
public byte[] readFileBytes(String filename) throws IOException
{
Path path = Paths.get(filename);
return Files.readAllBytes(path);
}
public PublicKey readPublicKey(String filename) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException
{
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(readFileBytes(filename));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(publicSpec);
}
public PrivateKey readPrivateKey(String filename) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException
{
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(readFileBytes(filename));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}
With the public and private keys, you can encrypt and decrypt small amounts of data (that fit within your RSA modulus.) I recommend OAEP padding.
public byte[] encrypt(PublicKey key, byte[] plaintext) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException
{
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(plaintext);
}
public byte[] decrypt(PrivateKey key, byte[] ciphertext) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException
{
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(ciphertext);
}
Here it is tied together with a simple encryption and decryption:
public void Hello()
{
try
{
PublicKey publicKey = readPublicKey("public.der");
PrivateKey privateKey = readPrivateKey("private.der");
byte[] message = "Hello World".getBytes("UTF8");
byte[] secret = encrypt(publicKey, message);
byte[] recovered_message = decrypt(privateKey, secret);
System.out.println(new String(recovered_message, "UTF8"));
}
catch (Exception e)
{
e.printStackTrace();
}
}
I would like to share a piece of code.. well actually a whole class which can do what you require if you customize it to your own needs. I have used this in one of my application where I used to encrypt/decrypt a file with the generated public/private keys. Same can be applied to Strings as well.
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.io.*;
import java.util.*;
/**
* This class encrypts and decrypts a file using CipherStreams
* and a 256-bit Rijndael key. The key is then encrypted using
* a 1024-bit RSA key, which is password-encrypted.
*/
public class FileEncryptorRSA {
/**
* When files are encrypted, this will be appended to the end
* of the filename.
*/
private static final String ENCRYPTED_FILENAME_SUFFIX=".encrypted";
/**
* When files are decrypted, this will be appended to the end
* of the filename.
*/
private static final String DECRYPTED_FILENAME_SUFFIX=".decrypted";
/**
* Number of times the password will be hashed with MD5
* when transforming it into a TripleDES key.
*/
private static final int ITERATIONS = 1000;
/**
* FileEncryptor is started with one of three options:
*
* -c: create key pair and write it to 2 files
* -e: encrypt a file, given as an argument
* -d: decrypt a file, given as an argument
*/
public static void main (String[] args)
throws Exception {
if ((args.length < 1) || (args.length > 2)) {
usage();
} else if ("-c".equals(args[0])) {
createKey();
} else if ("-e".equals(args[0])) {
encrypt(args[1]);
} else if ("-d".equals(args[0])) {
decrypt(args[1]);
} else {
usage();
}
}
private static void usage() {
System.err.println("Usage: java FileEncryptor -c|-e|-d [filename]");
System.exit(1);
}
/**
* Creates a 1024 bit RSA key and stores it to
* the filesystem as two files.
*/
private static void createKey()
throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
System.out.print("Password to encrypt the private key: ");
String password = in.readLine();
System.out.println("Generating an RSA keypair...");
// Create an RSA key
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
System.out.println("Done generating the keypair.\n");
// Now we need to write the public key out to a file
System.out.print("Public key filename: ");
String publicKeyFilename = in.readLine();
// Get the encoded form of the public key so we can
// use it again in the future. This is X.509 by default.
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
// Write the encoded public key out to the filesystem
FileOutputStream fos = new FileOutputStream(publicKeyFilename);
fos.write(publicKeyBytes);
fos.close();
// Now we need to do the same thing with the private key,
// but we need to password encrypt it as well.
System.out.print("Private key filename: ");
String privateKeyFilename = in.readLine();
// Get the encoded form. This is PKCS#8 by default.
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
// Here we actually encrypt the private key
byte[] encryptedPrivateKeyBytes =
passwordEncrypt(password.toCharArray(),privateKeyBytes);
fos = new FileOutputStream(privateKeyFilename);
fos.write(encryptedPrivateKeyBytes);
fos.close();
}
/**
* Encrypt the given file with a session key encrypted with an
* RSA public key which will be read in from the filesystem.
*/
private static void encrypt(String fileInput)
throws Exception {
BufferedReader in = new BufferedReader
(new InputStreamReader(System.in));
System.out.print("Public Key to encrypt with: ");
String publicKeyFilename = in.readLine();
// Load the public key bytes
FileInputStream fis = new FileInputStream(publicKeyFilename);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int theByte = 0;
while ((theByte = fis.read()) != -1)
{
baos.write(theByte);
}
fis.close();
byte[] keyBytes = baos.toByteArray();
baos.close();
// Turn the encoded key into a real RSA public key.
// Public keys are encoded in X.509.
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
// Open up an output file for the output of the encryption
String fileOutput = fileInput + ENCRYPTED_FILENAME_SUFFIX;
DataOutputStream output = new DataOutputStream
(new FileOutputStream(fileOutput));
// Create a cipher using that key to initialize it
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);
// Now create a new 256 bit Rijndael key to encrypt the file itself.
// This will be the session key.
KeyGenerator rijndaelKeyGenerator = KeyGenerator.getInstance("Rijndael");
rijndaelKeyGenerator.init(256);
System.out.println("Generating session key...");
Key rijndaelKey = rijndaelKeyGenerator.generateKey();
System.out.println("Done generating key.");
// Encrypt the Rijndael key with the RSA cipher
// and write it to the beginning of the file.
byte[] encodedKeyBytes= rsaCipher.doFinal(rijndaelKey.getEncoded());
output.writeInt(encodedKeyBytes.length);
output.write(encodedKeyBytes);
// Now we need an Initialization Vector for the symmetric cipher in CBC mode
SecureRandom random = new SecureRandom();
byte[] iv = new byte[16];
random.nextBytes(iv);
// Write the IV out to the file.
output.write(iv);
IvParameterSpec spec = new IvParameterSpec(iv);
// Create the cipher for encrypting the file itself.
Cipher symmetricCipher = Cipher.getInstance("Rijndael/CBC/PKCS5Padding");
symmetricCipher.init(Cipher.ENCRYPT_MODE, rijndaelKey, spec);
CipherOutputStream cos = new CipherOutputStream(output, symmetricCipher);
System.out.println("Encrypting the file...");
FileInputStream input = new FileInputStream(fileInput);
theByte = 0;
while ((theByte = input.read()) != -1)
{
cos.write(theByte);
}
input.close();
cos.close();
System.out.println("File encrypted.");
return;
}
/**
* Decrypt the given file.
* Start by getting the RSA private key
* and decrypting the session key embedded
* in the file. Then decrypt the file with
* that session key.
*/
private static void decrypt(String fileInput)
throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
System.out.print("Private Key to decrypt with: ");
String privateKeyFilename = in.readLine();
System.out.print("Password for the private key: ");
String password = in.readLine();
// Load the private key bytes
FileInputStream fis = new FileInputStream(privateKeyFilename);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int theByte = 0;
while ((theByte = fis.read()) != -1)
{
baos.write(theByte);
}
fis.close();
byte[] keyBytes = baos.toByteArray();
baos.close();
keyBytes = passwordDecrypt(password.toCharArray(), keyBytes);
// Turn the encoded key into a real RSA private key.
// Private keys are encoded in PKCS#8.
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
// Create a cipher using that key to initialize it
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
// Read in the encrypted bytes of the session key
DataInputStream dis = new DataInputStream(new FileInputStream(fileInput));
byte[] encryptedKeyBytes = new byte[dis.readInt()];
dis.readFully(encryptedKeyBytes);
// Decrypt the session key bytes.
rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] rijndaelKeyBytes = rsaCipher.doFinal(encryptedKeyBytes);
// Transform the key bytes into an actual key.
SecretKey rijndaelKey = new SecretKeySpec(rijndaelKeyBytes, "Rijndael");
// Read in the Initialization Vector from the file.
byte[] iv = new byte[16];
dis.read(iv);
IvParameterSpec spec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("Rijndael/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, rijndaelKey, spec);
CipherInputStream cis = new CipherInputStream(dis, cipher);
System.out.println("Decrypting the file...");
FileOutputStream fos = new FileOutputStream(fileInput + DECRYPTED_FILENAME_SUFFIX);
// Read through the file, decrypting each byte.
theByte = 0;
while ((theByte = cis.read()) != -1)
{
fos.write(theByte);
}
cis.close();
fos.close();
System.out.println("Done.");
return;
}
/**
* Utility method to encrypt a byte array with a given password.
* Salt will be the first 8 bytes of the byte array returned.
*/
private static byte[] passwordEncrypt(char[] password, byte[] plaintext) throws Exception {
// Create the salt.
byte[] salt = new byte[8];
Random random = new Random();
random.nextBytes(salt);
// Create a PBE key and cipher.
PBEKeySpec keySpec = new PBEKeySpec(password);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithSHAAndTwofish-CBC");
SecretKey key = keyFactory.generateSecret(keySpec);
PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATIONS);
Cipher cipher = Cipher.getInstance("PBEWithSHAAndTwofish-CBC");
cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
// Encrypt the array
byte[] ciphertext = cipher.doFinal(plaintext);
// Write out the salt, then the ciphertext and return it.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(salt);
baos.write(ciphertext);
return baos.toByteArray();
}
/**
* Utility method to decrypt a byte array with a given password.
* Salt will be the first 8 bytes in the array passed in.
*/
private static byte[] passwordDecrypt(char[] password, byte[] ciphertext) throws Exception {
// Read in the salt.
byte[] salt = new byte[8];
ByteArrayInputStream bais = new ByteArrayInputStream(ciphertext);
bais.read(salt,0,8);
// The remaining bytes are the actual ciphertext.
byte[] remainingCiphertext = new byte[ciphertext.length-8];
bais.read(remainingCiphertext,0,ciphertext.length-8);
// Create a PBE cipher to decrypt the byte array.
PBEKeySpec keySpec = new PBEKeySpec(password);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithSHAAndTwofish-CBC");
SecretKey key = keyFactory.generateSecret(keySpec);
PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATIONS);
Cipher cipher = Cipher.getInstance("PBEWithSHAAndTwofish-CBC");
// Perform the actual decryption.
cipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
return cipher.doFinal(remainingCiphertext);
}
}
EDIT:
You will require to change Java policy of you JVM to Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction for using this code.
All related information regarding change in JAVA Policy can be found HERE
Here is a good example:
http://javadigest.wordpress.com/2012/08/26/rsa-encryption-example/
and there are many more. (Google for "java encrypt RSA example" if this link breaks.)
I can't seem to find an answer that actually works
Try the one linked above. If it doesn't work, please follow up with an edit or comment to say what is going wrong.
I can't imagine that there isn't some simple function to do such a trivial thing. :P
Well sorry, but your imagination must be broken :-)
In fact, it is not a trivial thing. And it is made more difficult by the fact that Java is trying to support a wide range of crypto functionality and crypto technology stacks using a single unified API.

Categories

Resources