diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/AttachmentCipherOutputStream.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/AttachmentCipherOutputStream.java deleted file mode 100644 index c82fea9dde..0000000000 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/AttachmentCipherOutputStream.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2014-2017 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ - -package org.whispersystems.signalservice.api.crypto; - -import org.whispersystems.signalservice.internal.util.Util; - -import java.io.IOException; -import java.io.OutputStream; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.Mac; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -public class AttachmentCipherOutputStream extends DigestingOutputStream { - - private final Cipher cipher; - private final Mac mac; - - public AttachmentCipherOutputStream(byte[] combinedKeyMaterial, - byte[] iv, - OutputStream outputStream) - throws IOException - { - super(outputStream); - try { - this.cipher = initializeCipher(); - this.mac = initializeMac(); - byte[][] keyParts = Util.split(combinedKeyMaterial, 32, 32); - - if (iv == null) { - this.cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyParts[0], "AES")); - } else { - this.cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyParts[0], "AES"), new IvParameterSpec(iv)); - } - - this.mac.init(new SecretKeySpec(keyParts[1], "HmacSHA256")); - - mac.update(cipher.getIV()); - super.write(cipher.getIV()); - } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { - throw new AssertionError(e); - } - } - - @Override - public void write(byte[] buffer) throws IOException { - write(buffer, 0, buffer.length); - } - - @Override - public void write(byte[] buffer, int offset, int length) throws IOException { - byte[] ciphertext = cipher.update(buffer, offset, length); - - if (ciphertext != null) { - mac.update(ciphertext); - super.write(ciphertext); - } - } - - @Override - public void write(int b) throws IOException { - byte[] input = new byte[1]; - input[0] = (byte) b; - write(input, 0, 1); - } - - @Override - public void close() throws IOException { - try { - byte[] ciphertext = cipher.doFinal(); - byte[] auth = mac.doFinal(ciphertext); - - super.write(ciphertext); - super.write(auth); - - super.close(); - } catch (IllegalBlockSizeException | BadPaddingException e) { - throw new AssertionError(e); - } - } - - private Mac initializeMac() { - try { - return Mac.getInstance("HmacSHA256"); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } - } - - private Cipher initializeCipher() { - try { - return Cipher.getInstance("AES/CBC/PKCS5Padding"); - } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { - throw new AssertionError(e); - } - } -} diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/AttachmentCipherOutputStream.kt b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/AttachmentCipherOutputStream.kt new file mode 100644 index 0000000000..858104d7bf --- /dev/null +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/AttachmentCipherOutputStream.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2014-2017 Open Whisper Systems + * + * Licensed according to the LICENSE file in this repository. + */ +package org.whispersystems.signalservice.api.crypto + +import org.whispersystems.signalservice.internal.util.Util +import java.io.IOException +import java.io.OutputStream +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.Mac +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec + +/** + * An OutputStream for encrypting attachment data. + * The output stream writes the IV, ciphertext, and HMAC in sequence. + * + * @param combinedKeyMaterial The key material used for encryption and authentication. It is expected to be a byte array + * containing two parts: the first half being the AES key and the second half being the HMAC key. + * @param iv The initialization vector (IV) for the cipher, or null to generate a random one. + * @param outputStream The underlying output stream to write the encrypted data to. + */ +class AttachmentCipherOutputStream( + combinedKeyMaterial: ByteArray, + iv: ByteArray?, + outputStream: OutputStream +) : DigestingOutputStream(outputStream) { + + private val cipher: Cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") + private val mac: Mac = Mac.getInstance("HmacSHA256") + + init { + val keyParts = Util.split(combinedKeyMaterial, 32, 32) + + if (iv == null) { + cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(keyParts[0], "AES")) + } else { + cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(keyParts[0], "AES"), IvParameterSpec(iv)) + } + + mac.init(SecretKeySpec(keyParts[1], "HmacSHA256")) + + mac.update(cipher.iv) + super.write(cipher.iv) + } + + @Throws(IOException::class) + override fun write(buffer: ByteArray) { + write(buffer, 0, buffer.size) + } + + @Throws(IOException::class) + override fun write(buffer: ByteArray, offset: Int, length: Int) { + val ciphertext = cipher.update(buffer, offset, length) + + if (ciphertext != null) { + mac.update(ciphertext) + super.write(ciphertext) + } + } + + @Throws(IOException::class) + override fun write(b: Int) { + val input = ByteArray(1) + input[0] = b.toByte() + write(input, 0, 1) + } + + @Throws(IOException::class) + override fun close() { + try { + val ciphertext = cipher.doFinal() + val auth = mac.doFinal(ciphertext) + + super.write(ciphertext) + super.write(auth) + + super.close() + } catch (e: IllegalBlockSizeException) { + throw AssertionError(e) + } catch (e: BadPaddingException) { + throw AssertionError(e) + } + } +}