Skip to content

Commit

Permalink
Avoid an unnecessary copy on encrypt and decrypt in AesSiv.
Browse files Browse the repository at this point in the history
- For encryption, we first create the final ciphertext, and then let the aesCtr cipher write its output directly there into the correct location.
- For decryption, we let aesCtr directly read from the correct location.

PiperOrigin-RevId: 646146249
Change-Id: I3cec17ac47275082c294683fe31605917897e755
  • Loading branch information
juergw authored and copybara-github committed Jun 24, 2024
1 parent e13941d commit ff90fb9
Showing 1 changed file with 20 additions and 8 deletions.
28 changes: 20 additions & 8 deletions src/main/java/com/google/crypto/tink/subtle/AesSiv.java
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ private byte[] s2v(final byte[]... s) throws GeneralSecurityException {
@Override
public byte[] encryptDeterministically(final byte[] plaintext, final byte[] associatedData)
throws GeneralSecurityException {
if (plaintext.length > Integer.MAX_VALUE - AesUtil.BLOCK_SIZE) {
if (plaintext.length > Integer.MAX_VALUE - outputPrefix.length - AesUtil.BLOCK_SIZE) {
throw new GeneralSecurityException("plaintext too long");
}

Expand All @@ -155,9 +155,21 @@ public byte[] encryptDeterministically(final byte[] plaintext, final byte[] asso
new SecretKeySpec(this.aesCtrKey, "AES"),
new IvParameterSpec(ivForJavaCrypto));

byte[] ctrCiphertext = aesCtr.doFinal(plaintext);

return com.google.crypto.tink.subtle.Bytes.concat(outputPrefix, computedIv, ctrCiphertext);
int outputSize = outputPrefix.length + computedIv.length + plaintext.length;
byte[] output = Arrays.copyOf(outputPrefix, outputSize);
System.arraycopy(
/* src= */ computedIv,
/* srcPos= */ 0,
/* dest= */ output,
/* destPos= */ outputPrefix.length,
/* length= */ computedIv.length);
int written =
aesCtr.doFinal(
plaintext, 0, plaintext.length, output, outputPrefix.length + computedIv.length);
if (written != plaintext.length) {
throw new GeneralSecurityException("not enough data written");
}
return output;
}

@Override
Expand Down Expand Up @@ -185,10 +197,10 @@ public byte[] decryptDeterministically(final byte[] ciphertext, final byte[] ass
new SecretKeySpec(this.aesCtrKey, "AES"),
new IvParameterSpec(ivForJavaCrypto));

byte[] ctrCiphertext =
Arrays.copyOfRange(ciphertext, AesUtil.BLOCK_SIZE + outputPrefix.length, ciphertext.length);
byte[] decryptedPt = aesCtr.doFinal(ctrCiphertext);
if (ctrCiphertext.length == 0 && decryptedPt == null && SubtleUtil.isAndroid()) {
int offset = AesUtil.BLOCK_SIZE + outputPrefix.length;
int ctrCiphertextLen = ciphertext.length - offset;
byte[] decryptedPt = aesCtr.doFinal(ciphertext, offset, ctrCiphertextLen);
if (ctrCiphertextLen == 0 && decryptedPt == null && SubtleUtil.isAndroid()) {
// On Android KitKat (19) and Lollipop (21), Cipher.doFinal returns a null pointer when the
// ciphertext is empty, instead of an empty plaintext. Here we attempt to fix this bug. This
// is safe because if the plaintext is not empty, the next integrity check would reject it.
Expand Down

0 comments on commit ff90fb9

Please sign in to comment.