diff --git a/kms/src/main/java/com/example/Asymmetric.java b/kms/src/main/java/com/example/Asymmetric.java index d4989b27ee2..e9902b31d1a 100644 --- a/kms/src/main/java/com/example/Asymmetric.java +++ b/kms/src/main/java/com/example/Asymmetric.java @@ -32,7 +32,6 @@ import com.google.api.services.cloudkms.v1.model.ListKeyRingsResponse; import java.io.IOException; import java.io.StringReader; -import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.MessageDigest; @@ -43,7 +42,6 @@ import java.security.Signature; import java.security.SignatureException; import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; import javax.crypto.BadPaddingException; @@ -57,7 +55,18 @@ public class Asymmetric { // [START kms_get_asymmetric_public] - /** Retrieves the public key from a saved asymmetric key pair on Cloud KMS */ + /** + * Retrieves the public key from a saved asymmetric key pair on Cloud KMS + * + * Requires: + * java.io.StringReader + * java.security.KeyFactory + * java.security.PublicKey + * java.security.Security + * java.security.spec.X509EncodedKeySpec + * org.bouncycastle.jce.provider.BouncyCastleProvider + * org.bouncycastle.util.io.pem.PemReader + */ public static PublicKey getAsymmetricPublicKey(CloudKMS client, String keyPath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException { @@ -86,9 +95,9 @@ public static PublicKey getAsymmetricPublicKey(CloudKMS client, String keyPath) * Decrypt a given ciphertext using an 'RSA_DECRYPT_OAEP_2048_SHA256' private key * stored on Cloud KMS */ - public static String decryptRSA(String ciphertext, CloudKMS client, String keyPath) + public static byte[] decryptRSA(byte[] ciphertext, CloudKMS client, String keyPath) throws IOException { - AsymmetricDecryptRequest request = new AsymmetricDecryptRequest().setCiphertext(ciphertext); + AsymmetricDecryptRequest request = new AsymmetricDecryptRequest().encodeCiphertext(ciphertext); AsymmetricDecryptResponse response = client.projects() .locations() .keyRings() @@ -96,16 +105,22 @@ public static String decryptRSA(String ciphertext, CloudKMS client, String keyPa .cryptoKeyVersions() .asymmetricDecrypt(keyPath, request) .execute(); - return new String(response.decodePlaintext()); + return response.decodePlaintext(); } // [END kms_decrypt_rsa] // [START kms_encrypt_rsa] /** - * Encrypt message locally using an 'RSA_DECRYPT_OAEP_2048_SHA256' public key - * retrieved from Cloud KMS + * Encrypt data locally using an 'RSA_DECRYPT_OAEP_2048_SHA256' public key + * retrieved from Cloud KMS + * + * Requires: + * java.security.PublicKey + * java.security.Security + * javax.crypto.Cipher + * org.bouncycastle.jce.provider.BouncyCastleProvider */ - public static String encryptRSA(String message, CloudKMS client, String keyPath) + public static byte[] encryptRSA(byte[] plaintext, CloudKMS client, String keyPath) throws IOException, IllegalBlockSizeException, NoSuchPaddingException, InvalidKeySpecException, NoSuchProviderException, BadPaddingException, NoSuchAlgorithmException, InvalidKeyException { @@ -114,20 +129,23 @@ public static String encryptRSA(String message, CloudKMS client, String keyPath) Cipher cipher = Cipher.getInstance("RSA/NONE/OAEPWITHSHA256ANDMGF1PADDING", "BC"); cipher.init(Cipher.ENCRYPT_MODE, rsaKey); - byte[] ciphertext = cipher.doFinal(message.getBytes(StandardCharsets.UTF_8)); - return Base64.getEncoder().encodeToString(ciphertext); + return cipher.doFinal(plaintext); } // [END kms_encrypt_rsa] // [START kms_sign_asymmetric] - /** Create a signature for a message using a private key stored on Cloud KMS */ - public static String signAsymmetric(String message, CloudKMS client, String keyPath) + /** Create a signature for a message using a private key stored on Cloud KMS + * + * Requires: + * java.security.MessageDigest + * java.util.Base64 + */ + public static byte[] signAsymmetric(byte[] message, CloudKMS client, String keyPath) throws IOException, NoSuchAlgorithmException { - byte[] msgBytes = message.getBytes(StandardCharsets.UTF_8); Digest digest = new Digest(); // Note: some key algorithms will require a different hash function // For example, EC_SIGN_P384_SHA384 requires SHA-384 - digest.encodeSha256(MessageDigest.getInstance("SHA-256").digest(msgBytes)); + digest.encodeSha256(MessageDigest.getInstance("SHA-256").digest(message)); AsymmetricSignRequest signRequest = new AsymmetricSignRequest(); signRequest.setDigest(digest); @@ -139,16 +157,22 @@ public static String signAsymmetric(String message, CloudKMS client, String keyP .cryptoKeyVersions() .asymmetricSign(keyPath, signRequest) .execute(); - return response.getSignature(); + return Base64.getMimeDecoder().decode(response.getSignature()); } // [END kms_sign_asymmetric] // [START kms_verify_signature_rsa] /** * Verify the validity of an 'RSA_SIGN_PSS_2048_SHA256' signature for the - * specified plaintext message + * specified message + * + * Requires: + * java.security.PublicKey + * java.security.Security + * java.security.Signature + * org.bouncycastle.jce.provider.BouncyCastleProvider */ - public static boolean verifySignatureRSA(String signature, String message, CloudKMS client, + public static boolean verifySignatureRSA(byte[] signature, byte[] message, CloudKMS client, String keyPath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, SignatureException, NoSuchProviderException, InvalidKeyException { Security.addProvider(new BouncyCastleProvider()); @@ -157,18 +181,23 @@ public static boolean verifySignatureRSA(String signature, String message, Cloud Signature rsaVerify = Signature.getInstance("SHA256withRSA/PSS"); rsaVerify.initVerify(rsaKey); - rsaVerify.update(message.getBytes(StandardCharsets.UTF_8)); - byte[] sigBytes = Base64.getMimeDecoder().decode(signature); - return rsaVerify.verify(sigBytes); + rsaVerify.update(message); + return rsaVerify.verify(signature); } // [END kms_verify_signature_rsa] // [START kms_verify_signature_ec] /** * Verify the validity of an 'EC_SIGN_P256_SHA256' signature for the - * specified plaintext message + * specified message + * + * Requires: + * java.security.PublicKey + * java.security.Security + * java.security.Signature + * org.bouncycastle.jce.provider.BouncyCastleProvider */ - public static boolean verifySignatureEC(String signature, String message, CloudKMS client, + public static boolean verifySignatureEC(byte[] signature, byte[] message, CloudKMS client, String keyPath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, SignatureException, NoSuchProviderException, InvalidKeyException { Security.addProvider(new BouncyCastleProvider()); @@ -177,9 +206,8 @@ public static boolean verifySignatureEC(String signature, String message, CloudK Signature ecVerify = Signature.getInstance("SHA256withECDSA", "BC"); ecVerify.initVerify(ecKey); - ecVerify.update(message.getBytes(StandardCharsets.UTF_8)); - byte[] sigBytes = Base64.getMimeDecoder().decode(signature); - return ecVerify.verify(sigBytes); + ecVerify.update(message); + return ecVerify.verify(signature); } // [END kms_verify_signature_ec] diff --git a/kms/src/test/java/com/example/AsymmetricIT.java b/kms/src/test/java/com/example/AsymmetricIT.java index 0a991e6ec0c..635a36ac4cc 100644 --- a/kms/src/test/java/com/example/AsymmetricIT.java +++ b/kms/src/test/java/com/example/AsymmetricIT.java @@ -31,10 +31,13 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; +import java.nio.charset.StandardCharsets; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; +import java.util.Base64; import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -56,6 +59,7 @@ public class AsymmetricIT { private static final String parent = "projects/" + projectId + "/locations/global"; private static final String keyRing = "kms-asymmetric-sample"; private static final String message = "test message 123"; + private static final byte[] message_bytes = message.getBytes(StandardCharsets.UTF_8); private static final String rsaDecryptId = "rsa-decrypt"; private static final String rsaSignId = "rsa-sign"; @@ -136,35 +140,39 @@ public void testGetPublicKey() throws Exception { @Test public void testRSAEncryptDecrypt() throws Exception { - String ciphertext = Asymmetric.encryptRSA(message, client, rsaDecrypt); - assertEquals("incorrect RSA ciphertext length.", 344, ciphertext.length()); - assertEquals("incorrect ciphertext final character.", '=', ciphertext.charAt(343)); + byte[] ciphertext = Asymmetric.encryptRSA(message_bytes, client, rsaDecrypt); + assertEquals("incorrect RSA ciphertext length.", 256, ciphertext.length); - String plaintext = Asymmetric.decryptRSA(ciphertext, client, rsaDecrypt); + byte[] plainbytes = Asymmetric.decryptRSA(ciphertext, client, rsaDecrypt); + assertTrue("decryption failed.", Arrays.equals(message_bytes, plainbytes)); + String plaintext = new String(plainbytes); assertEquals("decryption failed.", message, plaintext); } @Test public void testRSASignVerify() throws Exception { - String sig = Asymmetric.signAsymmetric(message, client, rsaSign); - assertEquals("invalid ciphertext length", 344, sig.length()); - assertEquals("incorrect ciphertext final character.", '=', sig.charAt(343)); - - boolean success = Asymmetric.verifySignatureRSA(sig, message, client, rsaSign); - assertTrue("RSA verification failed.", success); - boolean shouldFail = Asymmetric.verifySignatureRSA(sig, message + ".", client, rsaSign); - assertFalse("RSA verification failed.", shouldFail); + byte[] sig = Asymmetric.signAsymmetric(message_bytes, client, rsaSign); + assertEquals("invalid ciphertext length", 256, sig.length); + + boolean success = Asymmetric.verifySignatureRSA(sig, message_bytes, client, rsaSign); + assertTrue("RSA verification failed. Valid message not accepted", success); + String changed = message + "."; + byte[] changedBytes = changed.getBytes(StandardCharsets.UTF_8); + boolean shouldFail = Asymmetric.verifySignatureRSA(sig, changedBytes, client, rsaSign); + assertFalse("RSA verification failed. Invalid message accepted", shouldFail); } @Test public void testECSignVerify() throws Exception { - String sig = Asymmetric.signAsymmetric(message, client, ecSign); - assertTrue("invalid ciphertext length", sig.length() > 50 && sig.length() < 300); - - boolean success = Asymmetric.verifySignatureEC(sig, message, client, ecSign); - assertTrue("RSA verification failed.", success); - boolean shouldFail = Asymmetric.verifySignatureEC(sig, message + ".", client, ecSign); - assertFalse("RSA verification failed.", shouldFail); + byte[] sig = Asymmetric.signAsymmetric(message_bytes, client, ecSign); + assertTrue("invalid ciphertext length", sig.length > 50 && sig.length < 300); + + boolean success = Asymmetric.verifySignatureEC(sig, message_bytes, client, ecSign); + assertTrue("EC verification failed. Valid message not accepted", success); + String changed = message + "."; + byte[] changedBytes = changed.getBytes(StandardCharsets.UTF_8); + boolean shouldFail = Asymmetric.verifySignatureEC(sig, changedBytes, client, ecSign); + assertFalse("EC verification failed. Invalid message accepted", shouldFail); } }