Aws-sdk-java: [KMS issue]Error happens when convert Bytebuffer to String

Created on 5 May 2017  路  9Comments  路  Source: aws/aws-sdk-java

I am doing a demo of encrypt and decrypt message with KMS. The encrypt and decrypt work is done in different jvm, so I need to store the encrypted message as String and pass it to the decryption side. But I always get the following error when decrypting:
Exception in thread "main" com.amazonaws.services.kms.model.InvalidCiphertextException: null (Service: AWSKMS; Status Code: 400; Error Code: InvalidCiphertextException;

My code is as follows:
````
public static void main(String[] args) throws UnsupportedEncodingException {

    AWSCredentials credentials = new BasicAWSCredentials("**********",
            "*********");
    AWSCredentialsProvider provider = new StaticCredentialsProvider(credentials);
    AWSKMS kms = AWSKMSClientBuilder.standard().withCredentials(provider).withRegion(Regions.EU_WEST_1).build();        
    String keyId = "arn:aws:kms:eu-west-1:*********:key/*********";
            //encrypt
    String text = "hello world";
    ByteBuffer plaintext1 = getByteBuffer(text);
    EncryptRequest req1 = new EncryptRequest().withKeyId(keyId).withPlaintext(plaintext1);
    ByteBuffer ciphertext = kms.encrypt(req1).getCiphertextBlob();
    String encrypted = getString(ciphertext);
            //decrypt
    ByteBuffer encryptedText = getByteBuffer(encrypted);
    DecryptRequest req2 = new DecryptRequest().withCiphertextBlob(encryptedText);
    ByteBuffer plaintext2 = kms.decrypt(req2).getPlaintext();
    String decrypted = getString(plaintext2);
    System.out.println(decrypted);
}
public static ByteBuffer getByteBuffer(String str) {
    return ByteBuffer.wrap(str.getBytes());
}
public static String getString(ByteBuffer b) {
    byte[] byteArray = new byte[b.remaining()];
    b.get(byteArray);
    return new String(byteArray);
}

````

guidance

Most helpful comment

It's not really an issue with KMS. The original issue is that the ciphertext returned by KMS Encrypt was being converted from a ByteBuffer to a String then back to a ByteBuffer for decrypting. Depending on the character encoding used, converting from the ByteBuffer to String back to ByteBuffer might not result in the two buffers being equal, essentially corrupting the encrypted data blob. Here's a short snippet to illustrate:

byte[] cipherText = new byte[]{ (byte) -50, (byte) -87 };

String bytesToString = new String(cipherText, StandardCharsets.US_ASCII);
byte[] stringToBytes = bytesToString.getBytes(StandardCharsets.US_ASCII);

System.out.printf("cipherText content: %s, stringToBytes content: %s. Equal?: %s%n",
        Arrays.toString(cipherText), Arrays.toString(stringToBytes), Arrays.equals(cipherText, stringToBytes));

with the output:

cipherText content: [-50, -87], stringToBytes content: [63, 63]. Equal?: false

Since ASCII doesn't define negative values, there's some conversion going on and it seems to just convert them to '?'. ISO_8859_1 is an 8-bit encoding so my guess is Java is not dropping any bits and just returning the data as-is.

If you need to convert the CipherText returned by KMS to a String, the safest way to do it is to encode and decode to/from a Base64 representation.

All 9 comments

The problem is caused by wrong charset. It is solved when I use ISO_8859_1 charset as follows.
private static Charset charset = StandardCharsets.ISO_8859_1; public static String getString(ByteBuffer b) { byte[] byteArray = new byte[b.remaining()]; b.get(byteArray); return new String(byteArray, charset); } public static ByteBuffer getByteBuffer(String str) { return ByteBuffer.wrap(str.getBytes(charset)); }

So can we close this?

@kiiadi Yes, please close this. Thanks.

I've encountered this same issue trying to do the same thing and resolved in the same way, but I'm wondering if we can discuss. Is the need for ISO_8859_1 an idiosyncrasy of the SDK or did @jiangchuan1220 and I both do something wrong in our code?

Specifically this is an issue when calling AWSKMS.decrypt.

@kiiadi Do you have any insight?

Did I miss the documentation that stated this charset is required?

It's not really an issue with KMS. The original issue is that the ciphertext returned by KMS Encrypt was being converted from a ByteBuffer to a String then back to a ByteBuffer for decrypting. Depending on the character encoding used, converting from the ByteBuffer to String back to ByteBuffer might not result in the two buffers being equal, essentially corrupting the encrypted data blob. Here's a short snippet to illustrate:

byte[] cipherText = new byte[]{ (byte) -50, (byte) -87 };

String bytesToString = new String(cipherText, StandardCharsets.US_ASCII);
byte[] stringToBytes = bytesToString.getBytes(StandardCharsets.US_ASCII);

System.out.printf("cipherText content: %s, stringToBytes content: %s. Equal?: %s%n",
        Arrays.toString(cipherText), Arrays.toString(stringToBytes), Arrays.equals(cipherText, stringToBytes));

with the output:

cipherText content: [-50, -87], stringToBytes content: [63, 63]. Equal?: false

Since ASCII doesn't define negative values, there's some conversion going on and it seems to just convert them to '?'. ISO_8859_1 is an 8-bit encoding so my guess is Java is not dropping any bits and just returning the data as-is.

If you need to convert the CipherText returned by KMS to a String, the safest way to do it is to encode and decode to/from a Base64 representation.

Face the same exception.Decoded the data array by Base64. However it still gave me the same exception. What is this exception really mean?

Face the same exceptions:
botocore.errorfactory.InvalidCiphertextException: An error occurred (InvalidCiphertextException) when calling the Decrypt operation:

Used:
key = b64decode(key)
response = client.decrypt(
CiphertextBlob=key
)

@jiangchuan1220
Thanks for the Charset solution you offered.
Works!

Was this page helpful?
0 / 5 - 0 ratings