Switch payload encryption to AES-GCM

This also ensures that the IV is generated with proper randomness.
This commit is contained in:
Andreas Straub 2015-07-21 01:15:32 +02:00
parent 971aa3a11e
commit 122bc97ce2
3 changed files with 46 additions and 17 deletions

View File

@ -4,6 +4,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.whispersystems.libaxolotl.AxolotlAddress; import org.whispersystems.libaxolotl.AxolotlAddress;
import org.whispersystems.libaxolotl.DuplicateMessageException; import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.IdentityKey; import org.whispersystems.libaxolotl.IdentityKey;
@ -30,6 +31,7 @@ import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
import org.whispersystems.libaxolotl.util.KeyHelper; import org.whispersystems.libaxolotl.util.KeyHelper;
import java.security.Security;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -678,6 +680,9 @@ public class AxolotlService {
} }
public AxolotlService(Account account, XmppConnectionService connectionService) { public AxolotlService(Account account, XmppConnectionService connectionService) {
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
}
this.mXmppConnectionService = connectionService; this.mXmppConnectionService = connectionService;
this.account = account; this.account = account;
this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService); this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService);
@ -1053,8 +1058,14 @@ public class AxolotlService {
} else { } else {
content = message.getBody(); content = message.getBody();
} }
final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(message.getContact().getJid().toBareJid(), final XmppAxolotlMessage axolotlMessage;
try {
axolotlMessage = new XmppAxolotlMessage(message.getContact().getJid().toBareJid(),
getOwnDeviceId(), content); getOwnDeviceId(), content);
} catch (CryptoFailedException e) {
Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
return null;
}
if(findSessionsforContact(message.getContact()).isEmpty()) { if(findSessionsforContact(message.getContact()).isEmpty()) {
return null; return null;
@ -1143,7 +1154,12 @@ public class AxolotlService {
byte[] payloadKey = session.processReceiving(header); byte[] payloadKey = session.processReceiving(header);
if (payloadKey != null) { if (payloadKey != null) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got payload key from axolotl header. Decrypting message..."); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got payload key from axolotl header. Decrypting message...");
try{
plaintextMessage = message.decrypt(session, payloadKey, session.getFingerprint()); plaintextMessage = message.decrypt(session, payloadKey, session.getFingerprint());
} catch (CryptoFailedException e) {
Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage());
break;
}
} }
Integer preKeyId = session.getPreKeyId(); Integer preKeyId = session.getPreKeyId();
if (preKeyId != null) { if (preKeyId != null) {

View File

@ -0,0 +1,7 @@
package eu.siacs.conversations.crypto.axolotl;
public class CryptoFailedException extends Exception {
public CryptoFailedException(Exception e){
super(e);
}
}

View File

@ -6,6 +6,8 @@ import android.util.Base64;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -107,26 +109,30 @@ public class XmppAxolotlMessage {
} }
} }
public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) { public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) throws CryptoFailedException{
this.from = from; this.from = from;
this.sourceDeviceId = sourceDeviceId; this.sourceDeviceId = sourceDeviceId;
this.headers = new HashSet<>(); this.headers = new HashSet<>();
this.encrypt(plaintext); this.encrypt(plaintext);
} }
private void encrypt(String plaintext) { private void encrypt(String plaintext) throws CryptoFailedException {
try { try {
KeyGenerator generator = KeyGenerator.getInstance("AES"); KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(128); generator.init(128);
SecretKey secretKey = generator.generateKey(); SecretKey secretKey = generator.generateKey();
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); SecureRandom random = new SecureRandom();
cipher.init(Cipher.ENCRYPT_MODE, secretKey); this.iv = new byte[16];
random.nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
this.innerKey = secretKey.getEncoded(); this.innerKey = secretKey.getEncoded();
this.iv = cipher.getIV();
this.ciphertext = cipher.doFinal(plaintext.getBytes()); this.ciphertext = cipher.doFinal(plaintext.getBytes());
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
| IllegalBlockSizeException | BadPaddingException e) { | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException
| InvalidAlgorithmParameterException e) {
throw new CryptoFailedException(e);
} }
} }
@ -174,11 +180,11 @@ public class XmppAxolotlMessage {
} }
public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key, String fingerprint) { public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key, String fingerprint) throws CryptoFailedException {
XmppAxolotlPlaintextMessage plaintextMessage = null; XmppAxolotlPlaintextMessage plaintextMessage = null;
try { try {
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv); IvParameterSpec ivSpec = new IvParameterSpec(iv);
@ -189,8 +195,8 @@ public class XmppAxolotlMessage {
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
| InvalidAlgorithmParameterException | IllegalBlockSizeException | InvalidAlgorithmParameterException | IllegalBlockSizeException
| BadPaddingException e) { | BadPaddingException | NoSuchProviderException e) {
throw new AssertionError(e); throw new CryptoFailedException(e);
} }
return plaintextMessage; return plaintextMessage;
} }