Public-key cryptography or asymmetric cryptography is something we use every daily, take for example the TLS on this very website.
From wikipedia
In a public-key encryption system, anyone with a public key can encrypt a message, yielding a ciphertext, but only those who know the corresponding private key can decrypt the ciphertext to obtain the original message.[8]
For example, a journalist can publish the public key of an encryption key pair on a web site so that sources can send secret messages to the news organization in ciphertext.
Wikipedia gives a good example. Another example:
You have a process sending data from your premises to another datacenter. Apart from encryption in transit you want to go an extra step and encrypt the contents, ensuring that only certain workloads in the datacenter can decrypt the data. This could be done with a symmetric key. The issue with a symmetric key is that both parties will have access to the same key, able to encrypt and decrypt information. Both parties might be uneasy to share the same key, plus we have an one direction flow: on premises datacenter to external datacenter.
This is where asymmetric encryption can be used. A public key encrypts the data before they get dispatched and the receiver of the data has the right private key to decrypt the data.
Let’s generate some RSA keys
#generate the private key openssl genrsa -out keypair.pem 2048 #generate the public key openssl rsa -in keypair.pem -pubout -out publickey.crt
We have a public key that we can use to encrypt data and a private key to use to decrypt the encrypted data.
The following java snippet reads the keys encrypts a string and decrypts it
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class Main {
public static void main(String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
final String privateKeyText;
final String publicKeyText;
try(InputStream privateKeyInputstream = new FileInputStream( "/path/to/keypair.pem");
InputStream publicKeyInputStream = new FileInputStream("/path/to/publickey.crt");) {
privateKeyText = new String(privateKeyInputstream.readAllBytes());
publicKeyText = new String(publicKeyInputStream.readAllBytes());
}
String privateKeyBase64 = privateKeyText.replaceAll("\\n", "").replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "");
String publicKeyBase64 = publicKeyText.replaceAll("\\n", "").replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "");
byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyBase64);
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyBase64);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
String unEncryptedText = "un-encrypted text";
byte[] message = unEncryptedText.getBytes(StandardCharsets.UTF_8);
Cipher encryptCypher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
OAEPParameterSpec oaepParams =
new OAEPParameterSpec(
"SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT);
encryptCypher.init(Cipher.ENCRYPT_MODE, publicKey, oaepParams);
encryptCypher.update(message);
byte[] ciphertext = encryptCypher.doFinal();
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey, oaepParams);
cipher.update(ciphertext);
byte[] decrypted = cipher.doFinal();
String decryptedText = new String(decrypted);
assert decryptedText.equals(unEncryptedText);
}
}
To wrap up the above example we read the contents of the keys. The keys are stored in a Base64 format thus we decode them and we create the keys using the decoded bytes. Then using the cipher and the keys we encrypt and decrypt the payload.
On the public cloud we have KMS.
We can use the AWS or the GCP KMS to create a private-public key pair.
Let’s start with the GCP example first. Once we created an asymmetric KMS we can download the public key.
Should we want to decrypt any data it will only happen through the KMS api. The public key can be found through the console.
We shall place the key on the classpath with the name gcp.crt
We shall encrypt he data using the public key downloaded and decrypt them using the KMS API:
import com.google.cloud.kms.v1.*;
import com.google.protobuf.ByteString;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class GCPKMS {
public static void main(String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) {
String name = "full-gcp-key-name";
String unEncryptedText = "un-encrypted text";
InputStream publicKeyInputStream = Main.class.getClassLoader().getResourceAsStream("gcp.crt");
String publicKeyText = new String(publicKeyInputStream.readAllBytes());
String publicKeyBase64 = publicKeyText.replaceAll("\\n", "").replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "");
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyBase64);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
java.security.PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(keySpec);
Cipher encryptCypher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
OAEPParameterSpec oaepParams =
new OAEPParameterSpec(
"SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT);
encryptCypher.init(Cipher.ENCRYPT_MODE, publicKey, oaepParams);
byte[] ciphertext = encryptCypher.doFinal(unEncryptedText.getBytes(StandardCharsets.UTF_8));
AsymmetricDecryptResponse response = client.asymmetricDecrypt(AsymmetricDecryptRequest.newBuilder()
.setCiphertext(ByteString.copyFrom(ciphertext))
.setName(name)
.build());
assert response.getPlaintext().toStringUtf8().equals(unEncryptedText);
}
}
}
Pretty similar with AWS KMS
Once we created the asymmetric key we can download the public key from console. 👁 Image
The key shall be placed on the classpath with the name `aws.crt`
We shall encrypt he data using the public key downloaded and decrypt them using the KMS API:
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.kms.KmsClient;
import software.amazon.awssdk.services.kms.model.DecryptRequest;
import software.amazon.awssdk.services.kms.model.EncryptionAlgorithmSpec;
import javax.crypto.Cipher;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class AWSKMS {
public static void main(String[] args) throws Exception {
String keyId = "kms-arn";
KmsClient kmsClient = KmsClient.builder()
.region(Region.of("us-central-1"))
.credentialsProvider(DefaultCredentialsProvider.builder()
.build())
.build();
String unEncryptedText = "un-encrypted text";
InputStream publicKeyInputStream = Main.class.getClassLoader().getResourceAsStream("aws.crt");
String publicKeyText = new String(publicKeyInputStream.readAllBytes());
String publicKeyBase64 = publicKeyText.replaceAll("\\n", "").replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "");
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyBase64);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
java.security.PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(keySpec);
Cipher encryptCypher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
OAEPParameterSpec oaepParams =
new OAEPParameterSpec(
"SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT);
encryptCypher.init(Cipher.ENCRYPT_MODE, publicKey, oaepParams);
byte[] ciphertext = encryptCypher.doFinal(unEncryptedText.getBytes(StandardCharsets.UTF_8));
var resp = kmsClient.decrypt(DecryptRequest.builder()
.keyId(keyId)
.encryptionAlgorithm(EncryptionAlgorithmSpec.RSAES_OAEP_SHA_256)
.ciphertextBlob(SdkBytes.fromByteArray(ciphertext))
.build());
assert resp.plaintext().asUtf8String().equals(unEncryptedText);
}
}
That’s it! We did asymmetric encryption and decryption locally using the rsa keys we created. We did asymmetric encryption using a GCP KMS public key and decrypted using the GCP KMS api. Lastly we did asymmetric encryption using an AWS KMS public key and decrypted using the AWS KMS api.
