From 1cc2762656904c4c2a64ad594424bdc014342ac0 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 19 Aug 2013 17:31:34 -0700 Subject: [PATCH] Refactor relationship between SessionCipher and Message. --- .../textsecure/crypto/SessionCipher.java | 299 ++++++++++++------ .../textsecure/crypto/TransportDetails.java | 6 +- .../crypto/protocol/EncryptedMessage.java | 170 ++++++++++ .../textsecure/crypto/protocol/Message.java | 151 --------- .../securesms/crypto/DecryptingQueue.java | 12 +- .../crypto/KeyExchangeProcessor.java | 10 +- .../crypto/protocol/KeyExchangeMessage.java | 8 +- .../securesms/mms/PngTransport.java | 207 ------------ .../securesms/mms/TextTransport.java | 10 +- .../securesms/sms/SmsTransportDetails.java | 6 +- .../transport/BaseTransportDetails.java | 44 --- .../securesms/transport/MmsTransport.java | 10 +- .../securesms/transport/PushTransport.java | 17 +- .../securesms/transport/SmsTransport.java | 7 +- 14 files changed, 420 insertions(+), 537 deletions(-) create mode 100644 library/src/org/whispersystems/textsecure/crypto/protocol/EncryptedMessage.java delete mode 100644 library/src/org/whispersystems/textsecure/crypto/protocol/Message.java delete mode 100644 src/org/thoughtcrime/securesms/mms/PngTransport.java delete mode 100644 src/org/thoughtcrime/securesms/transport/BaseTransportDetails.java diff --git a/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java b/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java index 0a6cb5cc10..c93cd8831b 100644 --- a/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java +++ b/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java @@ -22,7 +22,7 @@ import android.util.Log; import org.spongycastle.crypto.AsymmetricCipherKeyPair; import org.spongycastle.crypto.agreement.ECDHBasicAgreement; import org.spongycastle.crypto.params.ECPublicKeyParameters; -import org.whispersystems.textsecure.crypto.protocol.Message; +import org.whispersystems.textsecure.crypto.protocol.EncryptedMessage; import org.whispersystems.textsecure.storage.CanonicalRecipientAddress; import org.whispersystems.textsecure.storage.InvalidKeyIdException; import org.whispersystems.textsecure.storage.LocalKeyRecord; @@ -37,7 +37,6 @@ import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; -import java.io.IOException; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; @@ -57,85 +56,97 @@ public class SessionCipher { public static final int CIPHER_KEY_LENGTH = 16; public static final int MAC_KEY_LENGTH = 20; - public static final int ENCRYPTED_MESSAGE_OVERHEAD = Message.HEADER_LENGTH + MessageMac.MAC_LENGTH; - // public static final int ENCRYPTED_SINGLE_MESSAGE_BODY_MAX_SIZE = SmsTransportDetails.SINGLE_MESSAGE_MAX_BYTES - ENCRYPTED_MESSAGE_OVERHEAD; - - private final LocalKeyRecord localRecord; - private final RemoteKeyRecord remoteRecord; - private final SessionRecord sessionRecord; - private final MasterSecret masterSecret; - private final TransportDetails transportDetails; - - public SessionCipher(Context context, MasterSecret masterSecret, CanonicalRecipientAddress recipient, TransportDetails transportDetails) { - Log.w("SessionCipher", "Constructing session cipher..."); - this.masterSecret = masterSecret; - this.localRecord = new LocalKeyRecord(context, masterSecret, recipient); - this.remoteRecord = new RemoteKeyRecord(context, recipient); - this.sessionRecord = new SessionRecord(context, masterSecret, recipient); - this.transportDetails = transportDetails; - } - - public byte[] encryptMessage(byte[] messageText) { - Log.w("SessionCipher", "Encrypting message..."); - try { - int localId = localRecord.getCurrentKeyPair().getId(); - int remoteId = remoteRecord.getCurrentRemoteKey().getId(); - SessionKey sessionKey = getSessionKey(Cipher.ENCRYPT_MODE, localId, remoteId); - byte[]paddedMessage = transportDetails.getPaddedMessageBody(messageText); - byte[]cipherText = getCiphertext(paddedMessage, sessionKey.getCipherKey()); - byte[]message = buildMessageFromCiphertext(cipherText); - byte[]messageWithMac = MessageMac.buildMessageWithMac(message, sessionKey.getMacKey()); + public static final int ENCRYPTED_MESSAGE_OVERHEAD = EncryptedMessage.HEADER_LENGTH + MessageMac.MAC_LENGTH; - sessionRecord.setSessionKey(sessionKey); - sessionRecord.incrementCounter(); - sessionRecord.save(); - - return transportDetails.encodeMessage(messageWithMac); - } catch (IllegalBlockSizeException e) { - throw new IllegalArgumentException(e); - } catch (BadPaddingException e) { - throw new IllegalArgumentException(e); + public SessionCipherContext getEncryptionContext(Context context, MasterSecret masterSecret, + CanonicalRecipientAddress recipient) + { + try { + KeyRecords records = getKeyRecords(context, masterSecret, recipient); + int localKeyId = records.getLocalKeyRecord().getCurrentKeyPair().getId(); + int remoteKeyId = records.getRemoteKeyRecord().getCurrentRemoteKey().getId(); + SessionKey sessionKey = getSessionKey(masterSecret, Cipher.ENCRYPT_MODE, records, localKeyId, remoteKeyId); + PublicKey nextKey = records.getLocalKeyRecord().getNextKeyPair().getPublicKey(); + int counter = records.getSessionRecord().getCounter(); + int negotiatedVersion = records.getSessionRecord().getSessionVersion(); + + return new SessionCipherContext(records, sessionKey, localKeyId, remoteKeyId, nextKey, counter, negotiatedVersion); } catch (InvalidKeyIdException e) { throw new IllegalArgumentException(e); } } - + + public SessionCipherContext getDecryptionContext(Context context, MasterSecret masterSecret, + CanonicalRecipientAddress recipient, + int senderKeyId, int recipientKeyId, + PublicKey nextKey, int counter, + int negotiatedVersion) + throws InvalidMessageException + { + try { + KeyRecords records = getKeyRecords(context, masterSecret, recipient); + SessionKey sessionKey = getSessionKey(masterSecret, Cipher.DECRYPT_MODE, records, recipientKeyId, senderKeyId); + return new SessionCipherContext(records, sessionKey, senderKeyId, recipientKeyId, nextKey, counter, negotiatedVersion); + } catch (InvalidKeyIdException e) { + throw new InvalidMessageException(e); + } + } + + public byte[] encrypt(SessionCipherContext context, byte[] paddedMessageBody) { + Log.w("SessionCipher", "Encrypting message..."); + try { + byte[]cipherText = getCiphertext(paddedMessageBody, context.getSessionKey().getCipherKey(), context.getSessionRecord().getCounter()); + + context.getSessionRecord().setSessionKey(context.getSessionKey()); + context.getSessionRecord().incrementCounter(); + context.getSessionRecord().save(); + + return cipherText; + } catch (IllegalBlockSizeException e) { + throw new IllegalArgumentException(e); + } catch (BadPaddingException e) { + throw new IllegalArgumentException(e); + } + } - public byte[] decryptMessage(byte[] messageText) throws InvalidMessageException { + public byte[] decrypt(SessionCipherContext context, byte[] decodedCiphertext) + throws InvalidMessageException + { Log.w("SessionCipher", "Decrypting message..."); try { - byte[] decodedMessage = transportDetails.decodeMessage(messageText); - Message message = new Message(MessageMac.getMessageWithoutMac(decodedMessage)); - SessionKey sessionKey = getSessionKey(Cipher.DECRYPT_MODE, message.getReceiverKeyId(), message.getSenderKeyId()); + byte[] plaintextWithPadding = getPlaintext(decodedCiphertext, context.getSessionKey().getCipherKey(), context.getCounter()); + + context.getRemoteKeyRecord().updateCurrentRemoteKey(context.getNextKey()); + context.getRemoteKeyRecord().save(); - MessageMac.verifyMac(decodedMessage, sessionKey.getMacKey()); + context.getLocalKeyRecord().advanceKeyIfNecessary(context.getRecipientKeyId()); + context.getLocalKeyRecord().save(); - byte[] plaintextWithPadding = getPlaintext(message.getMessageText(), sessionKey.getCipherKey(), message.getCounter()); - byte[] plaintext = transportDetails.stripPaddedMessage(plaintextWithPadding); + context.getSessionRecord().setSessionKey(context.getSessionKey()); + context.getSessionRecord().setSessionVersion(context.getNegotiatedVersion()); + context.getSessionRecord().save(); - remoteRecord.updateCurrentRemoteKey(message.getNextKey()); - remoteRecord.save(); - - localRecord.advanceKeyIfNecessary(message.getReceiverKeyId()); - localRecord.save(); - - sessionRecord.setSessionKey(sessionKey); - sessionRecord.setSessionVersion(message.getHighestMutuallySupportedVersion()); - sessionRecord.save(); - - return plaintext; - } catch (IOException e) { - throw new InvalidMessageException("Encoding Failure", e); - } catch (InvalidKeyIdException e) { - throw new InvalidMessageException("Bad Key ID", e); - } catch (InvalidMacException e) { - throw new InvalidMessageException("Bad MAC", e); + return plaintextWithPadding; } catch (IllegalBlockSizeException e) { throw new InvalidMessageException("assert", e); } catch (BadPaddingException e) { throw new InvalidMessageException("assert", e); } } + + public byte[] mac(SessionCipherContext context, byte[] formattedCiphertext) { + return MessageMac.buildMessageWithMac(formattedCiphertext, context.getSessionKey().getMacKey()); + } + + public void verifyMac(SessionCipherContext context, byte[] decodedCiphertext) + throws InvalidMessageException + { + try { + MessageMac.verifyMac(decodedCiphertext, context.getSessionKey().getMacKey()); + } catch (InvalidMacException e) { + throw new InvalidMessageException(e); + } + } private SecretKeySpec deriveMacSecret(SecretKeySpec key) { try { @@ -147,25 +158,14 @@ public class SessionCipher { throw new IllegalArgumentException("SHA-1 Not Supported!",e); } } - - private byte[] buildMessageFromCiphertext(byte[] cipherText) { - Message message = new Message(localRecord.getCurrentKeyPair().getId(), - remoteRecord.getCurrentRemoteKey().getId(), - localRecord.getNextKeyPair().getPublicKey(), - sessionRecord.getCounter(), - cipherText, sessionRecord.getSessionVersion(), Message.SUPPORTED_VERSION); - - return message.serialize(); - } - private byte[] getPlaintext(byte[] cipherText, SecretKeySpec key, int counter) throws IllegalBlockSizeException, BadPaddingException { Cipher cipher = getCipher(Cipher.DECRYPT_MODE, key, counter); return cipher.doFinal(cipherText); } - private byte[] getCiphertext(byte[] message, SecretKeySpec key) throws IllegalBlockSizeException, BadPaddingException { - Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, key, sessionRecord.getCounter()); + private byte[] getCiphertext(byte[] message, SecretKeySpec key, int counter) throws IllegalBlockSizeException, BadPaddingException { + Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, key, counter); return cipher.doFinal(message); } @@ -193,12 +193,16 @@ public class SessionCipher { } } - private SecretKeySpec deriveCipherSecret(int mode, BigInteger sharedSecret, int localKeyId, int remoteKeyId) throws InvalidKeyIdException { + private SecretKeySpec deriveCipherSecret(int mode, BigInteger sharedSecret, + KeyRecords records, int localKeyId, + int remoteKeyId) + throws InvalidKeyIdException + { byte[] sharedSecretBytes = sharedSecret.toByteArray(); byte[] derivedBytes = deriveBytes(sharedSecretBytes, 16 * 2); byte[] cipherSecret = new byte[16]; - boolean isLowEnd = isLowEnd(localKeyId, remoteKeyId); + boolean isLowEnd = isLowEnd(records, localKeyId, remoteKeyId); isLowEnd = (mode == Cipher.ENCRYPT_MODE ? isLowEnd : !isLowEnd); if (isLowEnd) { @@ -210,9 +214,11 @@ public class SessionCipher { return new SecretKeySpec(cipherSecret, "AES"); } - private boolean isLowEnd(int localKeyId, int remoteKeyId) throws InvalidKeyIdException { - ECPublicKeyParameters localPublic = (ECPublicKeyParameters)localRecord.getKeyPairForId(localKeyId).getPublicKey().getKey(); - ECPublicKeyParameters remotePublic = (ECPublicKeyParameters)remoteRecord.getKeyForId(remoteKeyId).getKey(); + private boolean isLowEnd(KeyRecords records, int localKeyId, int remoteKeyId) + throws InvalidKeyIdException + { + ECPublicKeyParameters localPublic = records.getLocalKeyRecord().getKeyPairForId(localKeyId).getPublicKey().getKey(); + ECPublicKeyParameters remotePublic = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey(); BigInteger local = localPublic.getQ().getX().toBigInteger(); BigInteger remote = remotePublic.getQ().getX().toBigInteger(); @@ -240,26 +246,133 @@ public class SessionCipher { return md.digest(); } - private SessionKey getSessionKey(int mode, int localKeyId, int remoteKeyId) throws InvalidKeyIdException { + private SessionKey getSessionKey(MasterSecret masterSecret, int mode, KeyRecords records, + int localKeyId, int remoteKeyId) + throws InvalidKeyIdException + { Log.w("SessionCipher", "Getting session key for local: " + localKeyId + " remote: " + remoteKeyId); - SessionKey sessionKey = sessionRecord.getSessionKey(localKeyId, remoteKeyId); - if (sessionKey != null) return sessionKey; + SessionKey sessionKey = records.getSessionRecord().getSessionKey(localKeyId, remoteKeyId); + + if (sessionKey != null) + return sessionKey; - BigInteger sharedSecret = calculateSharedSecret(localKeyId, remoteKeyId); - SecretKeySpec cipherKey = deriveCipherSecret(mode, sharedSecret, localKeyId, remoteKeyId); + BigInteger sharedSecret = calculateSharedSecret(records, localKeyId, remoteKeyId); + SecretKeySpec cipherKey = deriveCipherSecret(mode, sharedSecret, records, localKeyId, remoteKeyId); SecretKeySpec macKey = deriveMacSecret(cipherKey); return new SessionKey(localKeyId, remoteKeyId, cipherKey, macKey, masterSecret); } - private BigInteger calculateSharedSecret(int localKeyId, int remoteKeyId) throws InvalidKeyIdException { + private BigInteger calculateSharedSecret(KeyRecords records, int localKeyId, int remoteKeyId) + throws InvalidKeyIdException + { ECDHBasicAgreement agreement = new ECDHBasicAgreement(); - AsymmetricCipherKeyPair localKeyPair = localRecord.getKeyPairForId(localKeyId).getKeyPair(); - ECPublicKeyParameters remoteKey = remoteRecord.getKeyForId(remoteKeyId).getKey(); + AsymmetricCipherKeyPair localKeyPair = records.getLocalKeyRecord().getKeyPairForId(localKeyId).getKeyPair(); + ECPublicKeyParameters remoteKey = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey(); agreement.init(localKeyPair.getPrivate()); - BigInteger secret = KeyUtil.calculateAgreement(agreement, remoteKey); - - return secret; + + return KeyUtil.calculateAgreement(agreement, remoteKey); } + + private KeyRecords getKeyRecords(Context context, MasterSecret masterSecret, + CanonicalRecipientAddress recipient) + { + LocalKeyRecord localKeyRecord = new LocalKeyRecord(context, masterSecret, recipient); + RemoteKeyRecord remoteKeyRecord = new RemoteKeyRecord(context, recipient); + SessionRecord sessionRecord = new SessionRecord(context, masterSecret, recipient); + return new KeyRecords(localKeyRecord, remoteKeyRecord, sessionRecord); + } + + private static class KeyRecords { + private final LocalKeyRecord localKeyRecord; + private final RemoteKeyRecord remoteKeyRecord; + private final SessionRecord sessionRecord; + + public KeyRecords(LocalKeyRecord localKeyRecord, RemoteKeyRecord remoteKeyRecord, SessionRecord sessionRecord) { + this.localKeyRecord = localKeyRecord; + this.remoteKeyRecord = remoteKeyRecord; + this.sessionRecord = sessionRecord; + } + + private LocalKeyRecord getLocalKeyRecord() { + return localKeyRecord; + } + + private RemoteKeyRecord getRemoteKeyRecord() { + return remoteKeyRecord; + } + + private SessionRecord getSessionRecord() { + return sessionRecord; + } + } + + public static class SessionCipherContext { + private final LocalKeyRecord localKeyRecord; + private final RemoteKeyRecord remoteKeyRecord; + private final SessionRecord sessionRecord; + private final SessionKey sessionKey; + private final int senderKeyId; + private final int recipientKeyId; + private final PublicKey nextKey; + private final int counter; + private final int negotiatedVersion; + + public SessionCipherContext(KeyRecords records, + SessionKey sessionKey, + int senderKeyId, + int receiverKeyId, + PublicKey nextKey, + int counter, + int negotiatedVersion) + { + this.localKeyRecord = records.getLocalKeyRecord(); + this.remoteKeyRecord = records.getRemoteKeyRecord(); + this.sessionRecord = records.getSessionRecord(); + this.sessionKey = sessionKey; + this.senderKeyId = senderKeyId; + this.recipientKeyId = receiverKeyId; + this.nextKey = nextKey; + this.counter = counter; + this.negotiatedVersion = negotiatedVersion; + } + + public LocalKeyRecord getLocalKeyRecord() { + return localKeyRecord; + } + + public RemoteKeyRecord getRemoteKeyRecord() { + return remoteKeyRecord; + } + + public SessionRecord getSessionRecord() { + return sessionRecord; + } + + public SessionKey getSessionKey() { + return sessionKey; + } + + public PublicKey getNextKey() { + return nextKey; + } + + public int getCounter() { + return counter; + } + + public int getSenderKeyId() { + return senderKeyId; + } + + public int getRecipientKeyId() { + return recipientKeyId; + } + + public int getNegotiatedVersion() { + return negotiatedVersion; + } + } + } diff --git a/library/src/org/whispersystems/textsecure/crypto/TransportDetails.java b/library/src/org/whispersystems/textsecure/crypto/TransportDetails.java index b03398f9bc..9ecd0ced62 100644 --- a/library/src/org/whispersystems/textsecure/crypto/TransportDetails.java +++ b/library/src/org/whispersystems/textsecure/crypto/TransportDetails.java @@ -20,9 +20,9 @@ import java.io.IOException; public interface TransportDetails { - public byte[] stripPaddedMessage(byte[] messageWithPadding); + public byte[] getStrippedPaddingMessageBody(byte[] messageWithPadding); public byte[] getPaddedMessageBody(byte[] messageBody); - public byte[] encodeMessage(byte[] messageWithMac); - public byte[] decodeMessage(byte[] encodedMessageBytes) throws IOException; + public byte[] getEncodedMessage(byte[] messageWithMac); + public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException; } diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/EncryptedMessage.java b/library/src/org/whispersystems/textsecure/crypto/protocol/EncryptedMessage.java new file mode 100644 index 0000000000..13ad15ecbc --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/protocol/EncryptedMessage.java @@ -0,0 +1,170 @@ +/** + * Copyright (C) 2011 Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.whispersystems.textsecure.crypto.protocol; + +import android.content.Context; + +import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.InvalidMessageException; +import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.crypto.PublicKey; +import org.whispersystems.textsecure.crypto.SessionCipher; +import org.whispersystems.textsecure.crypto.SessionCipher.SessionCipherContext; +import org.whispersystems.textsecure.crypto.TransportDetails; +import org.whispersystems.textsecure.storage.CanonicalRecipientAddress; +import org.whispersystems.textsecure.util.Conversions; + +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Parses and serializes the encrypted message format. + * + * @author Moxie Marlinspike + */ + +public class EncryptedMessage { + + public static final int SUPPORTED_VERSION = 1; + + private static final int VERSION_LENGTH = 1; + private static final int SENDER_KEY_ID_LENGTH = 3; + private static final int RECEIVER_KEY_ID_LENGTH = 3; + private static final int NEXT_KEY_LENGTH = PublicKey.KEY_SIZE; + private static final int COUNTER_LENGTH = 3; + public static final int HEADER_LENGTH = VERSION_LENGTH + SENDER_KEY_ID_LENGTH + RECEIVER_KEY_ID_LENGTH + COUNTER_LENGTH + NEXT_KEY_LENGTH; + + private static final int VERSION_OFFSET = 0; + private static final int SENDER_KEY_ID_OFFSET = VERSION_OFFSET + VERSION_LENGTH; + private static final int RECEIVER_KEY_ID_OFFSET = SENDER_KEY_ID_OFFSET + SENDER_KEY_ID_LENGTH; + private static final int NEXT_KEY_OFFSET = RECEIVER_KEY_ID_OFFSET + RECEIVER_KEY_ID_LENGTH; + private static final int COUNTER_OFFSET = NEXT_KEY_OFFSET + NEXT_KEY_LENGTH; + private static final int TEXT_OFFSET = COUNTER_OFFSET + COUNTER_LENGTH; + + private final Context context; + private final MasterSecret masterSecret; + private final TransportDetails transportDetails; + + public EncryptedMessage(Context context, MasterSecret masterSecret, TransportDetails transportDetails) { + this.context = context.getApplicationContext(); + this.masterSecret = masterSecret; + this.transportDetails = transportDetails; + } + + public byte[] encrypt(CanonicalRecipientAddress recipient, byte[] plaintext) { + synchronized (SessionCipher.CIPHER_LOCK) { + byte[] paddedBody = transportDetails.getPaddedMessageBody(plaintext); + SessionCipher sessionCipher = new SessionCipher(); + SessionCipherContext sessionContext = sessionCipher.getEncryptionContext(context, masterSecret, recipient); + byte[] ciphertextBody = sessionCipher.encrypt(sessionContext, paddedBody); + byte[] formattedCiphertext = getFormattedCiphertext(sessionContext, ciphertextBody); + byte[] ciphertextMessage = sessionCipher.mac(sessionContext, formattedCiphertext); + + return transportDetails.getEncodedMessage(ciphertextMessage); + } + } + + public byte[] decrypt(CanonicalRecipientAddress recipient, byte[] ciphertext) + throws InvalidMessageException + { + synchronized (SessionCipher.CIPHER_LOCK) { + try { + byte[] decodedMessage = transportDetails.getDecodedMessage(ciphertext); + int messageVersion = getMessageVersion(decodedMessage); + + if (messageVersion > SUPPORTED_VERSION) { + throw new InvalidMessageException("Unsupported version: " + messageVersion); + } + + int supportedVersion = getSupportedVersion(decodedMessage); + int receiverKeyId = getReceiverKeyId(decodedMessage); + int senderKeyId = getSenderKeyId(decodedMessage); + int counter = getCiphertextCounter(decodedMessage); + byte[] ciphertextBody = getMessageBody(decodedMessage); + PublicKey nextRemoteKey = getNextRemoteKey(decodedMessage); + int version = Math.min(supportedVersion, SUPPORTED_VERSION); + SessionCipher sessionCipher = new SessionCipher(); + SessionCipherContext sessionContext = sessionCipher.getDecryptionContext(context, masterSecret, + recipient, senderKeyId, + receiverKeyId, + nextRemoteKey, + counter, version); + + sessionCipher.verifyMac(sessionContext, decodedMessage); + + byte[] plaintextWithPadding = sessionCipher.decrypt(sessionContext, ciphertextBody); + return transportDetails.getStrippedPaddingMessageBody(plaintextWithPadding); + } catch (IOException e) { + throw new InvalidMessageException(e); + } catch (InvalidKeyException e) { + throw new InvalidMessageException(e); + } + } + } + + private byte[] getFormattedCiphertext(SessionCipherContext sessionContext, byte[] ciphertextBody) { + ByteBuffer buffer = ByteBuffer.allocate(HEADER_LENGTH + ciphertextBody.length); + byte versionByte = Conversions.intsToByteHighAndLow(sessionContext.getNegotiatedVersion(), SUPPORTED_VERSION); + byte[] senderKeyIdBytes = Conversions.mediumToByteArray(sessionContext.getSenderKeyId()); + byte[] receiverKeyIdBytes = Conversions.mediumToByteArray(sessionContext.getRecipientKeyId()); + byte[] nextKeyBytes = sessionContext.getNextKey().serialize(); + byte[] counterBytes = Conversions.mediumToByteArray(sessionContext.getCounter()); + + buffer.put(versionByte); + buffer.put(senderKeyIdBytes); + buffer.put(receiverKeyIdBytes); + buffer.put(nextKeyBytes); + buffer.put(counterBytes); + buffer.put(ciphertextBody); + + return buffer.array(); + } + + private int getMessageVersion(byte[] message) { + return Conversions.highBitsToInt(message[VERSION_OFFSET]); + } + + private int getSupportedVersion(byte[] message) { + return Conversions.lowBitsToInt(message[VERSION_OFFSET]); + } + + private int getSenderKeyId(byte[] message) { + return Conversions.byteArrayToMedium(message, SENDER_KEY_ID_OFFSET); + } + + private int getReceiverKeyId(byte[] message) { + return Conversions.byteArrayToMedium(message, RECEIVER_KEY_ID_OFFSET); + } + + private int getCiphertextCounter(byte[] message) { + return Conversions.byteArrayToMedium(message, COUNTER_OFFSET); + } + + private byte[] getMessageBody(byte[] message) { + byte[] body = new byte[message.length - HEADER_LENGTH]; + System.arraycopy(message, TEXT_OFFSET, body, 0, body.length); + + return body; + } + + private PublicKey getNextRemoteKey(byte[] message) throws InvalidKeyException { + byte[] key = new byte[NEXT_KEY_LENGTH]; + System.arraycopy(message, NEXT_KEY_OFFSET, key, 0, key.length); + + return new PublicKey(key); + } +} diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/Message.java b/library/src/org/whispersystems/textsecure/crypto/protocol/Message.java deleted file mode 100644 index 709d943e9c..0000000000 --- a/library/src/org/whispersystems/textsecure/crypto/protocol/Message.java +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Copyright (C) 2011 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.whispersystems.textsecure.crypto.protocol; - -import android.util.Log; - -import org.whispersystems.textsecure.crypto.InvalidKeyException; -import org.whispersystems.textsecure.crypto.InvalidMessageException; -import org.whispersystems.textsecure.crypto.PublicKey; -import org.whispersystems.textsecure.util.Conversions; - -import java.nio.ByteBuffer; - -/** - * Parses and serializes the encrypted message format. - * - * @author Moxie Marlinspike - */ - -public class Message { - - public static final int SUPPORTED_VERSION = 1; - - private static final int VERSION_LENGTH = 1; - private static final int SENDER_KEY_ID_LENGTH = 3; - private static final int RECEIVER_KEY_ID_LENGTH = 3; - private static final int NEXT_KEY_LENGTH = PublicKey.KEY_SIZE; - private static final int COUNTER_LENGTH = 3; - public static final int HEADER_LENGTH = VERSION_LENGTH + SENDER_KEY_ID_LENGTH + RECEIVER_KEY_ID_LENGTH + COUNTER_LENGTH + NEXT_KEY_LENGTH; - - private static final int VERSION_OFFSET = 0; - private static final int SENDER_KEY_ID_OFFSET = VERSION_OFFSET + VERSION_LENGTH; - private static final int RECEIVER_KEY_ID_OFFSET = SENDER_KEY_ID_OFFSET + SENDER_KEY_ID_LENGTH; - private static final int NEXT_KEY_OFFSET = RECEIVER_KEY_ID_OFFSET + RECEIVER_KEY_ID_LENGTH; - private static final int COUNTER_OFFSET = NEXT_KEY_OFFSET + NEXT_KEY_LENGTH; - private static final int TEXT_OFFSET = COUNTER_OFFSET + COUNTER_LENGTH; - - private int senderKeyId; - private int receiverKeyId; - private int counter; - private int messageVersion; - private int supportedVersion; - private byte[] message; - - private PublicKey nextKey; - - public Message(int senderKeyId, int receiverKeyId, PublicKey nextKey, int counter, byte[] message, int messageVersion, int supportedVersion) { - this.senderKeyId = senderKeyId; - this.receiverKeyId = receiverKeyId; - this.nextKey = nextKey; - this.counter = counter; - this.message = message; - this.messageVersion = messageVersion; - this.supportedVersion = supportedVersion; - } - - public Message(byte[] messageBytes) throws InvalidMessageException { - try { - if (messageBytes.length <= HEADER_LENGTH) - throw new InvalidMessageException("Message is shorter than headers."); - - this.messageVersion = Conversions.highBitsToInt(messageBytes[VERSION_OFFSET]); - this.supportedVersion = Conversions.lowBitsToInt(messageBytes[VERSION_OFFSET]); - - Log.w("Message", "Message Version: " + messageVersion); - Log.w("Message", "Supported Version: " + supportedVersion); - - if (messageVersion > SUPPORTED_VERSION) - throw new InvalidMessageException("Message protocol version not supported: " + messageVersion); - - this.senderKeyId = Conversions.byteArrayToMedium(messageBytes, SENDER_KEY_ID_OFFSET); - this.receiverKeyId = Conversions.byteArrayToMedium(messageBytes, RECEIVER_KEY_ID_OFFSET); - this.counter = Conversions.byteArrayToMedium(messageBytes, COUNTER_OFFSET); - - Log.w("Message", "Parsed current version: " + messageVersion + " supported version: " + supportedVersion); - - byte[] nextKeyBytes = new byte[NEXT_KEY_LENGTH]; - byte[] textBytes = new byte[messageBytes.length - HEADER_LENGTH]; - - System.arraycopy(messageBytes, NEXT_KEY_OFFSET, nextKeyBytes, 0, nextKeyBytes.length); - System.arraycopy(messageBytes, TEXT_OFFSET, textBytes, 0, textBytes.length); - - Log.w("Message", "Pulling next key out of message..."); - this.nextKey = new PublicKey(nextKeyBytes); - this.message = textBytes; - } catch (InvalidKeyException ike) { - throw new AssertionError(ike); - } - } - - public byte[] serialize() { - ByteBuffer buffer = ByteBuffer.allocate(HEADER_LENGTH + message.length); - - Log.w("Message", "Constructing Message Version: (" + messageVersion + "," + supportedVersion + ")"); - - byte versionByte = Conversions.intsToByteHighAndLow(messageVersion, supportedVersion); - byte[] senderKeyIdBytes = Conversions.mediumToByteArray(senderKeyId); - byte[] receiverKeyIdBytes = Conversions.mediumToByteArray(receiverKeyId); - Log.w("Message", "Serializing next key into message..."); - byte[] nextKeyBytes = nextKey.serialize(); - byte[] counterBytes = Conversions.mediumToByteArray(counter); - - buffer.put(versionByte); - buffer.put(senderKeyIdBytes); - buffer.put(receiverKeyIdBytes); - buffer.put(nextKeyBytes); - buffer.put(counterBytes); - buffer.put(message); - - return buffer.array(); - } - - public int getHighestMutuallySupportedVersion() { - return Math.min(SUPPORTED_VERSION, this.supportedVersion); - } - - public int getSenderKeyId() { - return this.senderKeyId; - } - - public int getReceiverKeyId() { - return this.receiverKeyId; - } - - public PublicKey getNextKey() { - return this.nextKey; - } - - public int getCounter() { - return this.counter; - } - - public byte[] getMessageText() { - return this.message; - } - -} diff --git a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java index d64c776e14..8f50d0c799 100644 --- a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java +++ b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java @@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.sms.SmsTransportDetails; import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidMessageException; import org.whispersystems.textsecure.crypto.SessionCipher; +import org.whispersystems.textsecure.crypto.protocol.EncryptedMessage; import org.whispersystems.textsecure.util.Hex; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.WorkerThread; @@ -194,9 +195,10 @@ public class DecryptingQueue { synchronized (SessionCipher.CIPHER_LOCK) { Log.w("DecryptingQueue", "Decrypting: " + Hex.toString(ciphertextPduBytes)); - SessionCipher cipher = new SessionCipher(context, masterSecret, recipient, new TextTransport()); + EncryptedMessage message = new EncryptedMessage(context, masterSecret, new TextTransport()); + try { - plaintextPduBytes = cipher.decryptMessage(ciphertextPduBytes); + plaintextPduBytes = message.decrypt(recipient, ciphertextPduBytes); } catch (InvalidMessageException ime) { // XXX - For some reason, Sprint seems to append a single character to the // end of message text segments. I don't know why, so here we just try @@ -205,7 +207,7 @@ public class DecryptingQueue { Log.w("DecryptingQueue", "Attempting truncated decrypt..."); byte[] truncated = new byte[ciphertextPduBytes.length - 1]; System.arraycopy(ciphertextPduBytes, 0, truncated, 0, truncated.length); - plaintextPduBytes = cipher.decryptMessage(truncated); + plaintextPduBytes = message.decrypt(recipient, truncated); } else { throw ime; } @@ -273,8 +275,8 @@ public class DecryptingQueue { return; } - SessionCipher cipher = new SessionCipher(context, masterSecret, recipient, new SmsTransportDetails()); - plaintextBody = new String(cipher.decryptMessage(body.getBytes())); + EncryptedMessage message = new EncryptedMessage(context, masterSecret, new SmsTransportDetails()); + plaintextBody = new String(message.decrypt(recipient, body.getBytes())); } catch (InvalidMessageException e) { Log.w("DecryptionQueue", e); database.markAsDecryptFailed(messageId); diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java index 82b39ab16b..7c02a67fa4 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java @@ -26,11 +26,11 @@ import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.PublicKey; +import org.whispersystems.textsecure.crypto.protocol.EncryptedMessage; import org.whispersystems.textsecure.push.PreKeyEntity; import org.whispersystems.textsecure.storage.LocalKeyRecord; import org.whispersystems.textsecure.storage.RemoteKeyRecord; import org.whispersystems.textsecure.storage.SessionRecord; -import org.whispersystems.textsecure.crypto.protocol.Message; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.sms.MessageSender; @@ -105,7 +105,7 @@ public class KeyExchangeProcessor { sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(), remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes()); sessionRecord.setIdentityKey(message.getIdentityKey()); - sessionRecord.setSessionVersion(Message.SUPPORTED_VERSION); + sessionRecord.setSessionVersion(EncryptedMessage.SUPPORTED_VERSION); sessionRecord.save(); DatabaseFactory.getIdentityDatabase(context) @@ -118,7 +118,7 @@ public class KeyExchangeProcessor { if (needsResponseFromUs()) { localKeyRecord = KeyUtil.initializeRecordFor(recipient, context, masterSecret); - KeyExchangeMessage ourMessage = new KeyExchangeMessage(context, masterSecret, Math.min(Message.SUPPORTED_VERSION, message.getMaxVersion()), localKeyRecord, initiateKeyId); + KeyExchangeMessage ourMessage = new KeyExchangeMessage(context, masterSecret, Math.min(EncryptedMessage.SUPPORTED_VERSION, message.getMaxVersion()), localKeyRecord, initiateKeyId); OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, ourMessage.serialize()); Log.w("KeyExchangeProcessor", "Responding with key exchange message fingerprint: " + ourMessage.getPublicKey().getFingerprint()); Log.w("KeyExchangeProcessor", "Which has a local key record fingerprint: " + localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprint()); @@ -132,9 +132,9 @@ public class KeyExchangeProcessor { sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(), remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes()); sessionRecord.setIdentityKey(message.getIdentityKey()); - sessionRecord.setSessionVersion(Math.min(Message.SUPPORTED_VERSION, message.getMaxVersion())); + sessionRecord.setSessionVersion(Math.min(EncryptedMessage.SUPPORTED_VERSION, message.getMaxVersion())); - Log.w("KeyExchangeUtil", "Setting session version: " + Math.min(Message.SUPPORTED_VERSION, message.getMaxVersion())); + Log.w("KeyExchangeUtil", "Setting session version: " + Math.min(EncryptedMessage.SUPPORTED_VERSION, message.getMaxVersion())); sessionRecord.save(); diff --git a/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java index c08312e4d0..a38bde8c3b 100644 --- a/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java +++ b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java @@ -25,8 +25,8 @@ import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.PublicKey; +import org.whispersystems.textsecure.crypto.protocol.EncryptedMessage; import org.whispersystems.textsecure.storage.LocalKeyRecord; -import org.whispersystems.textsecure.crypto.protocol.Message; import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Conversions; @@ -67,7 +67,7 @@ public class KeyExchangeMessage { public KeyExchangeMessage(Context context, MasterSecret masterSecret, int messageVersion, LocalKeyRecord record, int highIdBits) { this.publicKey = new PublicKey(record.getCurrentKeyPair().getPublicKey()); this.messageVersion = messageVersion; - this.supportedVersion = Message.SUPPORTED_VERSION; + this.supportedVersion = EncryptedMessage.SUPPORTED_VERSION; publicKey.setId(publicKey.getId() | (highIdBits << 12)); @@ -93,9 +93,9 @@ public class KeyExchangeMessage { this.supportedVersion = Conversions.lowBitsToInt(keyBytes[0]); this.serialized = messageBody; - if (messageVersion > Message.SUPPORTED_VERSION) + if (messageVersion > EncryptedMessage.SUPPORTED_VERSION) throw new InvalidVersionException("Key exchange with version: " + messageVersion + - " but we only support: " + Message.SUPPORTED_VERSION); + " but we only support: " + EncryptedMessage.SUPPORTED_VERSION); if (messageVersion >= 1) keyBytes = Base64.decodeWithoutPadding(messageBody); diff --git a/src/org/thoughtcrime/securesms/mms/PngTransport.java b/src/org/thoughtcrime/securesms/mms/PngTransport.java deleted file mode 100644 index 923e9ec1fc..0000000000 --- a/src/org/thoughtcrime/securesms/mms/PngTransport.java +++ /dev/null @@ -1,207 +0,0 @@ -/** - * Copyright (C) 2011 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.mms; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.zip.CRC32; -import java.util.zip.DeflaterOutputStream; -import java.util.zip.InflaterInputStream; - -import org.whispersystems.textsecure.crypto.SessionCipher; -import org.whispersystems.textsecure.crypto.TransportDetails; -import org.whispersystems.textsecure.util.Conversions; - -import android.util.Log; - -public class PngTransport implements TransportDetails { - - private static final int CRC_LENGTH = 4; - private static final int IHDR_WIDTH_OFFSET = 8; - private static final int IHDR_HEIGHT_OFFSET = 12; - private static final int IDAT_HEADER_LENGTH = 8; - - private static final byte[] PNG_HEADER = {(byte)0x89, (byte)0x50, (byte)0x4E, (byte)0x47, (byte)0x0D, (byte)0x0A, (byte)0x1A, (byte)0x0A}; - private static final byte[] IHDR_CHUNK = {(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0D, (byte)0x49, (byte)0x48, (byte)0x44, (byte)0x52, - (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, - (byte)0x10, (byte)0x06, (byte)0x00, (byte)0x00, (byte)0x00}; - private static final byte[] IEND_CHUNK = {(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x49, (byte)0x45, (byte)0x4E, (byte)0x44}; - private static final byte[] IDAT_TAG = {(byte)0x49, (byte)0x44, (byte)0x41, (byte)0x54}; - - private static final int BYTES_PER_ROW = 640 * 8; - private static final int IDAT_LENGTH_OFFSET = 33; - - private void readFully(InputStream in, byte[] buffer) throws IOException { - int totalRead = 0; - int read = 0; - - while (totalRead < buffer.length) { - read = in.read(buffer, totalRead, buffer.length-totalRead); - Log.w("PngTransport", "Read: " + read); - if (read == -1) - throw new IOException("Could not fill buffer!"); - totalRead+=read; - } - } - - private int getImageHeight(byte[] data) { - int rows = (data.length / BYTES_PER_ROW); - assert(data.length % BYTES_PER_ROW == 0); - return rows; - } - - private byte[] getIhdr(int width, int height) { - byte[] ihdr = new byte[IHDR_CHUNK.length]; - System.arraycopy(IHDR_CHUNK, 0, ihdr, 0, ihdr.length); - Conversions.intToByteArray(ihdr, IHDR_WIDTH_OFFSET, width); - Conversions.intToByteArray(ihdr, IHDR_HEIGHT_OFFSET, height); - return ihdr; - } - - private byte[] calculateChunkCrc(byte[] header, byte[] chunk) { - byte[] crcBytes = new byte[4]; - CRC32 crc = new CRC32(); - crc.update(header, 4, header.length - 4); - - if (chunk != null) - crc.update(chunk, 0, chunk.length); - - long crcValue = crc.getValue(); - Conversions.longTo4ByteArray(crcBytes, 0, crcValue); - - return crcBytes; - } - - private int readIhdr(ByteArrayInputStream bais) throws IOException { - byte[] ihdr = new byte[IHDR_CHUNK.length]; - bais.read(ihdr); - return Conversions.byteArrayToInt(ihdr, IHDR_HEIGHT_OFFSET); - } - - private byte[] readIdatChunk(ByteArrayInputStream bais, int rows) throws IOException { - InflaterInputStream iis = new InflaterInputStream(bais); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - byte[] buffer = new byte[BYTES_PER_ROW + 1]; - - Log.w("PngTransport", "Total image height: " + rows); - - for (int i=0;i=0;i--) { - if (messageWithPadding[i] == (byte)0x01) - break; - else if (i == 0) - throw new AssertionError("No padding!"); - } - - int paddingLength = messageWithPadding.length - i; - byte[] message = new byte[messageWithPadding.length - paddingLength]; - - System.arraycopy(messageWithPadding, 0, message, 0, message.length); - return message; - } -} diff --git a/src/org/thoughtcrime/securesms/mms/TextTransport.java b/src/org/thoughtcrime/securesms/mms/TextTransport.java index ad18fd3043..f1141ca371 100644 --- a/src/org/thoughtcrime/securesms/mms/TextTransport.java +++ b/src/org/thoughtcrime/securesms/mms/TextTransport.java @@ -23,19 +23,23 @@ import org.whispersystems.textsecure.util.Base64; public class TextTransport implements TransportDetails { - public byte[] decodeMessage(byte[] encodedMessageBytes) throws IOException { + @Override + public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException { return Base64.decode(encodedMessageBytes); } - public byte[] encodeMessage(byte[] messageWithMac) { + @Override + public byte[] getEncodedMessage(byte[] messageWithMac) { return Base64.encodeBytes(messageWithMac).getBytes(); } + @Override public byte[] getPaddedMessageBody(byte[] messageBody) { return messageBody; } - public byte[] stripPaddedMessage(byte[] messageWithPadding) { + @Override + public byte[] getStrippedPaddingMessageBody(byte[] messageWithPadding) { return messageWithPadding; } diff --git a/src/org/thoughtcrime/securesms/sms/SmsTransportDetails.java b/src/org/thoughtcrime/securesms/sms/SmsTransportDetails.java index 0a7283a9fc..9a835994d6 100644 --- a/src/org/thoughtcrime/securesms/sms/SmsTransportDetails.java +++ b/src/org/thoughtcrime/securesms/sms/SmsTransportDetails.java @@ -38,20 +38,20 @@ public class SmsTransportDetails implements TransportDetails { public static final int ENCRYPTED_SINGLE_MESSAGE_BODY_MAX_SIZE = SINGLE_MESSAGE_MAX_BYTES - SessionCipher.ENCRYPTED_MESSAGE_OVERHEAD; @Override - public byte[] encodeMessage(byte[] messageWithMac) { + public byte[] getEncodedMessage(byte[] messageWithMac) { String encodedMessage = Base64.encodeBytesWithoutPadding(messageWithMac); Log.w("SmsTransportDetails", "Encoded Message Length: " + encodedMessage.length()); return encodedMessage.getBytes(); } @Override - public byte[] decodeMessage(byte[] encodedMessageBytes) throws IOException { + public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException { String encodedMessage = new String(encodedMessageBytes); return Base64.decodeWithoutPadding(encodedMessage); } @Override - public byte[] stripPaddedMessage(byte[] messageWithPadding) { + public byte[] getStrippedPaddingMessageBody(byte[] messageWithPadding) { int paddingBeginsIndex = 0; for (int i=1;i