Migrate to Curve25519.

1) Generate a Curve25519 identity key.

2) Use Curve25519 ephemerals and identities for v2 3DHE agreements.

3) Initiate v2 key exchange messages.

4) Accept v1 key exchange messages.

5) TOFU Curve25519 identities.
This commit is contained in:
Moxie Marlinspike
2013-11-10 04:15:29 -08:00
parent a03fff8b24
commit c38a8aa699
57 changed files with 2197 additions and 498 deletions

View File

@@ -1,5 +1,6 @@
/**
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013 Open 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
@@ -19,8 +20,10 @@ package org.whispersystems.textsecure.crypto;
import android.os.Parcel;
import android.os.Parcelable;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.util.Hex;
import org.whispersystems.textsecure.util.Util;
/**
* A class for representing an identity key.
@@ -44,15 +47,15 @@ public class IdentityKey implements Parcelable, SerializableKey {
}
};
public static final int SIZE = 1 + KeyUtil.POINT_SIZE;
private static final int VERSION = 1;
private ECPublicKeyParameters publicKey;
public IdentityKey(ECPublicKeyParameters publicKey) {
public static final int SIZE = 1 + ECPublicKey.KEY_SIZE;
private static final int CURRENT_VESION = 1;
private ECPublicKey publicKey;
public IdentityKey(ECPublicKey publicKey) {
this.publicKey = publicKey;
}
public IdentityKey(Parcel in) throws InvalidKeyException {
int length = in.readInt();
byte[] serialized = new byte[length];
@@ -64,43 +67,42 @@ public class IdentityKey implements Parcelable, SerializableKey {
public IdentityKey(byte[] bytes, int offset) throws InvalidKeyException {
initializeFromSerialized(bytes, offset);
}
public ECPublicKeyParameters getPublicKeyParameters() {
return this.publicKey;
public ECPublicKey getPublicKey() {
return publicKey;
}
private void initializeFromSerialized(byte[] bytes, int offset) throws InvalidKeyException {
int version = bytes[offset] & 0xff;
int version = bytes[offset] & 0xff;
if (version > VERSION)
if (version > CURRENT_VESION)
throw new InvalidKeyException("Unsupported key version: " + version);
this.publicKey = KeyUtil.decodePoint(bytes, offset+1);
this.publicKey = Curve.decodePoint(bytes, offset + 1);
}
public byte[] serialize() {
byte[] encodedKey = KeyUtil.encodePoint(publicKey.getQ());
byte[] combined = new byte[1 + encodedKey.length];
combined[0] = (byte)VERSION;
System.arraycopy(encodedKey, 0, combined, 1, encodedKey.length);
return combined;
byte[] versionBytes = {(byte)CURRENT_VESION};
byte[] encodedKey = publicKey.serialize();
return Util.combine(versionBytes, encodedKey);
}
public String getFingerprint() {
return Hex.toString(serialize());
return Hex.toString(publicKey.serialize());
}
@Override
public boolean equals(Object other) {
if (other == null) return false;
if (!(other instanceof IdentityKey)) return false;
return publicKey.getQ().equals(((IdentityKey)other).publicKey.getQ());
return publicKey.equals(((IdentityKey) other).getPublicKey());
}
@Override
public int hashCode() {
return publicKey.getQ().hashCode();
return publicKey.hashCode();
}
public int describeContents() {

View File

@@ -16,7 +16,7 @@
*/
package org.whispersystems.textsecure.crypto;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey;
/**
* Holder for public and private identity key pair.
@@ -26,9 +26,9 @@ import org.spongycastle.crypto.params.ECPrivateKeyParameters;
public class IdentityKeyPair {
private final IdentityKey publicKey;
private final ECPrivateKeyParameters privateKey;
private final ECPrivateKey privateKey;
public IdentityKeyPair(IdentityKey publicKey, ECPrivateKeyParameters privateKey) {
public IdentityKeyPair(IdentityKey publicKey, ECPrivateKey privateKey) {
this.publicKey = publicKey;
this.privateKey = privateKey;
}
@@ -37,7 +37,7 @@ public class IdentityKeyPair {
return publicKey;
}
public ECPrivateKeyParameters getPrivateKey() {
public ECPrivateKey getPrivateKey() {
return privateKey;
}
}

View File

@@ -1,5 +1,6 @@
/**
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013 Open 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
@@ -16,13 +17,13 @@
*/
package org.whispersystems.textsecure.crypto;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.whispersystems.textsecure.util.Hex;
import android.util.Log;
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey;
import org.whispersystems.textsecure.util.Hex;
import org.whispersystems.textsecure.util.Util;
/**
* Represents a session's active KeyPair.
*
@@ -31,15 +32,15 @@ import android.util.Log;
public class KeyPair {
private ECPrivateKeyParameters privateKey;
private PublicKey publicKey;
private PublicKey publicKey;
private ECPrivateKey privateKey;
private final MasterCipher masterCipher;
public KeyPair(int keyPairId, AsymmetricCipherKeyPair keyPair, MasterSecret masterSecret) {
public KeyPair(int keyPairId, ECKeyPair keyPair, MasterSecret masterSecret) {
this.masterCipher = new MasterCipher(masterSecret);
this.publicKey = new PublicKey(keyPairId, (ECPublicKeyParameters)keyPair.getPublic());
this.privateKey = (ECPrivateKeyParameters)keyPair.getPrivate();
this.publicKey = new PublicKey(keyPairId, keyPair.getPublicKey());
this.privateKey = keyPair.getPrivateKey();
}
public KeyPair(byte[] bytes, MasterCipher masterCipher) throws InvalidKeyException {
@@ -54,11 +55,11 @@ public class KeyPair {
public PublicKey getPublicKey() {
return publicKey;
}
public AsymmetricCipherKeyPair getKeyPair() {
return new AsymmetricCipherKeyPair(publicKey.getKey(), privateKey);
public ECPrivateKey getPrivateKey() {
return privateKey;
}
public byte[] toBytes() {
return serialize();
}
@@ -67,18 +68,14 @@ public class KeyPair {
this.publicKey = new PublicKey(bytes);
byte[] privateKeyBytes = new byte[bytes.length - PublicKey.KEY_SIZE];
System.arraycopy(bytes, PublicKey.KEY_SIZE, privateKeyBytes, 0, privateKeyBytes.length);
this.privateKey = masterCipher.decryptKey(privateKeyBytes);
this.privateKey = masterCipher.decryptKey(this.publicKey.getType(), privateKeyBytes);
}
public byte[] serialize() {
byte[] publicKeyBytes = publicKey.serialize();
Log.w("KeyPair", "Serialized public key bytes: " + Hex.toString(publicKeyBytes));
byte[] privateKeyBytes = masterCipher.encryptKey(privateKey);
byte[] combined = new byte[publicKeyBytes.length + privateKeyBytes.length];
System.arraycopy(publicKeyBytes, 0, combined, 0, publicKeyBytes.length);
System.arraycopy(privateKeyBytes, 0, combined, publicKeyBytes.length, privateKeyBytes.length);
return combined;
byte[] privateKeyBytes = masterCipher.encryptKey(privateKey);
return Util.combine(publicKeyBytes, privateKeyBytes);
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013 Open 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
@@ -16,26 +16,17 @@
*/
package org.whispersystems.textsecure.crypto;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import android.content.Context;
import android.util.Log;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECFieldElement;
import org.spongycastle.math.ec.ECPoint;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
import org.whispersystems.textsecure.storage.LocalKeyRecord;
import org.whispersystems.textsecure.storage.RemoteKeyRecord;
import org.whispersystems.textsecure.storage.SessionRecord;
import android.content.Context;
import android.util.Log;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
/**
* Helper class for generating key pairs and calculating ECDH agreements.
@@ -45,52 +36,6 @@ import android.util.Log;
public class KeyUtil {
public static final BigInteger q = new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16);
private static final BigInteger a = new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16);
private static final BigInteger b = new BigInteger("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", 16);
private static final BigInteger n = new BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16);
private static final ECFieldElement x = new ECFieldElement.Fp(q, new BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16));
private static final ECFieldElement y = new ECFieldElement.Fp(q, new BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16));
private static final ECCurve curve = new ECCurve.Fp(q, a, b);
private static final ECPoint g = new ECPoint.Fp(curve, x, y, true);
public static final int POINT_SIZE = 33;
public static final ECDomainParameters domainParameters = new ECDomainParameters(curve, g, n);
public static byte[] encodePoint(ECPoint point) {
synchronized (curve) {
return point.getEncoded();
}
}
public static ECPublicKeyParameters decodePoint(byte[] encoded, int offset)
throws InvalidKeyException
{
byte[] pointBytes = new byte[POINT_SIZE];
System.arraycopy(encoded, offset, pointBytes, 0, pointBytes.length);
synchronized (curve) {
ECPoint Q;
try {
Q = curve.decodePoint(pointBytes);
} catch (RuntimeException re) {
throw new InvalidKeyException(re);
}
return new ECPublicKeyParameters(Q, KeyUtil.domainParameters);
}
}
public static BigInteger calculateAgreement(ECDHBasicAgreement agreement, ECPublicKeyParameters remoteKey) {
synchronized (curve) {
return agreement.calculateAgreement(remoteKey);
}
}
public static void abortSessionFor(Context context, CanonicalRecipientAddress recipient) {
//XXX Obviously we should probably do something more thorough here eventually.
Log.w("KeyUtil", "Aborting session, deleting keys...");
@@ -120,17 +65,18 @@ public class KeyUtil {
new SessionRecord(context, masterSecret, recipient).getIdentityKey() != null;
}
public static LocalKeyRecord initializeRecordFor(CanonicalRecipientAddress recipient,
Context context,
MasterSecret masterSecret)
public static LocalKeyRecord initializeRecordFor(Context context,
MasterSecret masterSecret,
CanonicalRecipientAddress recipient,
int sessionVersion)
{
Log.w("KeyUtil", "Initializing local key pairs...");
try {
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
int initialId = secureRandom.nextInt(4094) + 1;
KeyPair currentPair = new KeyPair(initialId, KeyUtil.generateKeyPair(), masterSecret);
KeyPair nextPair = new KeyPair(initialId + 1, KeyUtil.generateKeyPair(), masterSecret);
KeyPair currentPair = new KeyPair(initialId, Curve.generateKeyPairForSession(sessionVersion), masterSecret);
KeyPair nextPair = new KeyPair(initialId + 1, Curve.generateKeyPairForSession(sessionVersion), masterSecret);
LocalKeyRecord record = new LocalKeyRecord(context, masterSecret, recipient);
record.setCurrentKeyPair(currentPair);
@@ -143,30 +89,4 @@ public class KeyUtil {
}
}
public static AsymmetricCipherKeyPair generateKeyPair() {
try {
synchronized (curve) {
ECKeyGenerationParameters keyParamters = new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG"));
ECKeyPairGenerator generator = new ECKeyPairGenerator();
generator.init(keyParamters);
AsymmetricCipherKeyPair keyPair = generator.generateKeyPair();
return cloneKeyPairWithPointCompression(keyPair);
}
} catch (NoSuchAlgorithmException nsae) {
Log.w("keyutil", nsae);
return null;
}
}
// This is dumb, but the ECPublicKeys that the generator makes by default don't have point compression
// turned on, and there's no setter. Great.
private static AsymmetricCipherKeyPair cloneKeyPairWithPointCompression(AsymmetricCipherKeyPair keyPair) {
ECPublicKeyParameters publicKey = (ECPublicKeyParameters)keyPair.getPublic();
ECPoint q = publicKey.getQ();
return new AsymmetricCipherKeyPair(new ECPublicKeyParameters(new ECPoint.Fp(q.getCurve(), q.getX(), q.getY(), true), publicKey.getParameters()), keyPair.getPrivate());
}
}

View File

@@ -1,5 +1,6 @@
/**
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013 Open 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
@@ -16,8 +17,14 @@
*/
package org.whispersystems.textsecure.crypto;
import android.util.Log;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.Hex;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
@@ -32,12 +39,6 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.Hex;
import android.util.Log;
/**
* Class that handles encryption for local storage.
*
@@ -69,13 +70,11 @@ public class MasterCipher {
throw new AssertionError(e);
}
}
public byte[] encryptKey(ECPrivateKeyParameters params) {
BigInteger d = params.getD();
byte[] dBytes = d.toByteArray();
return encryptBytes(dBytes);
public byte[] encryptKey(ECPrivateKey privateKey) {
return encryptBytes(privateKey.serialize());
}
public String encryptBody(String body) {
return encryptAndEncodeBytes(body.getBytes());
}
@@ -84,13 +83,13 @@ public class MasterCipher {
return new String(decodeAndDecryptBytes(body));
}
public ECPrivateKeyParameters decryptKey(byte[] key) {
public ECPrivateKey decryptKey(int type, byte[] key)
throws org.whispersystems.textsecure.crypto.InvalidKeyException
{
try {
BigInteger d = new BigInteger(decryptBytes(key));
return new ECPrivateKeyParameters(d, KeyUtil.domainParameters);
return Curve.decodePrivatePoint(type, decryptBytes(key));
} catch (InvalidMessageException ime) {
Log.w("bodycipher", ime);
return null; // XXX
throw new org.whispersystems.textsecure.crypto.InvalidKeyException(ime);
}
}

View File

@@ -1,5 +1,6 @@
/**
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013 Open 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
@@ -57,14 +58,12 @@ public class MessageCipher {
try {
CiphertextMessage message = new CiphertextMessage(ciphertext);
int messageVersion = message.getCurrentVersion();
int supportedVersion = message.getSupportedVersion();
int negotiatedVersion = Math.min(supportedVersion, CiphertextMessage.SUPPORTED_VERSION);
int senderKeyId = message.getSenderKeyId();
int receiverKeyId = message.getReceiverKeyId();
PublicKey nextRemoteKey = new PublicKey(message.getNextKeyBytes());
int counter = message.getCounter();
byte[] body = message.getBody();
int messageVersion = message.getCurrentVersion();
int senderKeyId = message.getSenderKeyId();
int receiverKeyId = message.getReceiverKeyId();
PublicKey nextRemoteKey = new PublicKey(message.getNextKeyBytes());
int counter = message.getCounter();
byte[] body = message.getBody();
SessionCipher sessionCipher = new SessionCipher();
SessionCipherContext sessionContext = sessionCipher.getDecryptionContext(context, masterSecret,
@@ -73,8 +72,7 @@ public class MessageCipher {
receiverKeyId,
nextRemoteKey,
counter,
messageVersion,
negotiatedVersion);
messageVersion);
message.verifyMac(sessionContext);
@@ -84,5 +82,4 @@ public class MessageCipher {
}
}
}
}

View File

@@ -1,40 +1,53 @@
/**
* Copyright (C) 2013 Open 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 <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey;
import org.whispersystems.textsecure.util.Util;
public class PreKeyPair {
private final MasterCipher masterCipher;
private final ECPrivateKeyParameters privateKey;
private final PreKeyPublic publicKey;
private final MasterCipher masterCipher;
private final PreKeyPublic publicKey;
private final ECPrivateKey privateKey;
public PreKeyPair(MasterSecret masterSecret, AsymmetricCipherKeyPair keyPair) {
public PreKeyPair(MasterSecret masterSecret, ECKeyPair keyPair) {
this.masterCipher = new MasterCipher(masterSecret);
this.publicKey = new PreKeyPublic((ECPublicKeyParameters)keyPair.getPublic());
this.privateKey = (ECPrivateKeyParameters)keyPair.getPrivate();
this.publicKey = new PreKeyPublic(keyPair.getPublicKey());
this.privateKey = keyPair.getPrivateKey();
}
public PreKeyPair(MasterSecret masterSecret, byte[] serialized) throws InvalidKeyException {
if (serialized.length < KeyUtil.POINT_SIZE + 1)
throw new InvalidKeyException("Serialized length: " + serialized.length);
byte[] privateKeyBytes = new byte[serialized.length - KeyUtil.POINT_SIZE];
System.arraycopy(serialized, KeyUtil.POINT_SIZE, privateKeyBytes, 0, privateKeyBytes.length);
byte[] privateKeyBytes = new byte[serialized.length - PreKeyPublic.KEY_SIZE];
System.arraycopy(serialized, PreKeyPublic.KEY_SIZE, privateKeyBytes, 0, privateKeyBytes.length);
this.masterCipher = new MasterCipher(masterSecret);
this.publicKey = new PreKeyPublic(serialized, 0);
this.privateKey = masterCipher.decryptKey(privateKeyBytes);
this.privateKey = masterCipher.decryptKey(this.publicKey.getType(), privateKeyBytes);
}
public PreKeyPublic getPublicKey() {
return publicKey;
}
public AsymmetricCipherKeyPair getKeyPair() {
return new AsymmetricCipherKeyPair(publicKey.getPublicKey(), privateKey);
public ECKeyPair getKeyPair() {
return new ECKeyPair(publicKey.getPublicKey(), privateKey);
}
public byte[] serialize() {

View File

@@ -1,25 +1,49 @@
/**
* Copyright (C) 2013 Open 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 <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.util.Util;
public class PreKeyPublic {
private final ECPublicKeyParameters publicKey;
public static final int KEY_SIZE = ECPublicKey.KEY_SIZE;
public PreKeyPublic(ECPublicKeyParameters publicKey) {
private final ECPublicKey publicKey;
public PreKeyPublic(ECPublicKey publicKey) {
this.publicKey = publicKey;
}
public PreKeyPublic(byte[] serialized, int offset) throws InvalidKeyException {
this.publicKey = KeyUtil.decodePoint(serialized, offset);
this.publicKey = Curve.decodePoint(serialized, offset);
}
public byte[] serialize() {
return KeyUtil.encodePoint(publicKey.getQ());
return publicKey.serialize();
}
public ECPublicKeyParameters getPublicKey() {
public ECPublicKey getPublicKey() {
return publicKey;
}
public int getType() {
return this.publicKey.getType();
}
}

View File

@@ -1,9 +1,28 @@
/**
* Copyright (C) 2013 Open 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 <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto;
import android.content.Context;
import android.util.Log;
import com.google.thoughtcrimegson.Gson;
import org.whispersystems.textsecure.crypto.ecc.Curve25519;
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
import org.whispersystems.textsecure.storage.PreKeyRecord;
import org.whispersystems.textsecure.util.Medium;
@@ -21,7 +40,7 @@ import java.util.List;
public class PreKeyUtil {
public static final int BATCH_SIZE = 70;
public static final int BATCH_SIZE = 20;
public static List<PreKeyRecord> generatePreKeys(Context context, MasterSecret masterSecret) {
List<PreKeyRecord> records = new LinkedList<PreKeyRecord>();
@@ -29,7 +48,7 @@ public class PreKeyUtil {
for (int i=0;i<BATCH_SIZE;i++) {
int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE;
PreKeyPair keyPair = new PreKeyPair(masterSecret, KeyUtil.generateKeyPair());
PreKeyPair keyPair = new PreKeyPair(masterSecret, Curve25519.generateKeyPair());
PreKeyRecord record = new PreKeyRecord(context, masterSecret, preKeyId, keyPair);
record.save();
@@ -50,7 +69,7 @@ public class PreKeyUtil {
}
}
PreKeyPair keyPair = new PreKeyPair(masterSecret, KeyUtil.generateKeyPair());
PreKeyPair keyPair = new PreKeyPair(masterSecret, Curve25519.generateKeyPair());
PreKeyRecord record = new PreKeyRecord(context, masterSecret, Medium.MAX_VALUE, keyPair);
record.save();

View File

@@ -1,5 +1,6 @@
/**
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013 Open 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
@@ -18,17 +19,20 @@ package org.whispersystems.textsecure.crypto;
import android.util.Log;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.whispersystems.textsecure.util.Hex;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.util.Conversions;
import org.whispersystems.textsecure.util.Hex;
import org.whispersystems.textsecure.util.Util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class PublicKey {
public static final int KEY_SIZE = 3 + KeyUtil.POINT_SIZE;
private final ECPublicKeyParameters publicKey;
public static final int KEY_SIZE = 3 + ECPublicKey.KEY_SIZE;
private final ECPublicKey publicKey;
private int id;
public PublicKey(PublicKey publicKey) {
@@ -38,7 +42,7 @@ public class PublicKey {
this.publicKey = publicKey.publicKey;
}
public PublicKey(int id, ECPublicKeyParameters publicKey) {
public PublicKey(int id, ECPublicKey publicKey) {
this.publicKey = publicKey;
this.id = id;
}
@@ -50,16 +54,21 @@ public class PublicKey {
public PublicKey(byte[] bytes, int offset) throws InvalidKeyException {
Log.w("PublicKey", "PublicKey Length: " + (bytes.length - offset));
if ((bytes.length - offset) < KEY_SIZE)
throw new InvalidKeyException("Provided bytes are too short.");
this.id = Conversions.byteArrayToMedium(bytes, offset);
this.publicKey = KeyUtil.decodePoint(bytes, offset + 3);
this.publicKey = Curve.decodePoint(bytes, offset + 3);
}
public PublicKey(byte[] bytes) throws InvalidKeyException {
this(bytes, 0);
}
public int getType() {
return publicKey.getType();
}
public void setId(int id) {
this.id = id;
@@ -69,7 +78,7 @@ public class PublicKey {
return id;
}
public ECPublicKeyParameters getKey() {
public ECPublicKey getKey() {
return publicKey;
}
@@ -88,14 +97,11 @@ public class PublicKey {
}
public byte[] serialize() {
byte[] complete = new byte[KEY_SIZE];
byte[] serializedPoint = KeyUtil.encodePoint(publicKey.getQ());
byte[] keyIdBytes = Conversions.mediumToByteArray(id);
byte[] serializedPoint = publicKey.serialize();
Log.w("PublicKey", "Serializing public key point: " + Hex.toString(serializedPoint));
Conversions.mediumToByteArray(complete, 0, id);
System.arraycopy(serializedPoint, 0, complete, 3, serializedPoint.length);
return complete;
}
return Util.combine(keyIdBytes, serializedPoint);
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013 Open 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
@@ -19,7 +19,7 @@ package org.whispersystems.textsecure.crypto;
import android.content.Context;
import android.util.Log;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
@@ -30,17 +30,16 @@ import org.whispersystems.textsecure.storage.SessionKey;
import org.whispersystems.textsecure.storage.SessionRecord;
import org.whispersystems.textsecure.util.Conversions;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
* This is where the session encryption magic happens. Implements a compressed version of the OTR protocol.
@@ -61,19 +60,21 @@ public class SessionCipher {
CanonicalRecipientAddress recipient)
{
try {
KeyRecords records = getKeyRecords(context, masterSecret, recipient);
int localKeyId = records.getLocalKeyRecord().getCurrentKeyPair().getId();
int remoteKeyId = records.getRemoteKeyRecord().getCurrentRemoteKey().getId();
int negotiatedVersion = records.getSessionRecord().getSessionVersion();
SessionKey sessionKey = getSessionKey(masterSecret, Cipher.ENCRYPT_MODE, negotiatedVersion, localIdentityKey, records, localKeyId, remoteKeyId);
PublicKey nextKey = records.getLocalKeyRecord().getNextKeyPair().getPublicKey();
int counter = records.getSessionRecord().getCounter();
KeyRecords records = getKeyRecords(context, masterSecret, recipient);
int localKeyId = records.getLocalKeyRecord().getCurrentKeyPair().getId();
int remoteKeyId = records.getRemoteKeyRecord().getCurrentRemoteKey().getId();
int sessionVersion = records.getSessionRecord().getSessionVersion();
SessionKey sessionKey = getSessionKey(masterSecret, Cipher.ENCRYPT_MODE, sessionVersion, localIdentityKey, records, localKeyId, remoteKeyId);
PublicKey nextKey = records.getLocalKeyRecord().getNextKeyPair().getPublicKey();
int counter = records.getSessionRecord().getCounter();
return new SessionCipherContext(records, sessionKey, localKeyId, remoteKeyId,
nextKey, counter, negotiatedVersion, negotiatedVersion);
nextKey, counter, sessionVersion);
} catch (InvalidKeyIdException e) {
throw new IllegalArgumentException(e);
} catch (InvalidKeyException e) {
throw new IllegalArgumentException(e);
}
}
@@ -82,7 +83,7 @@ public class SessionCipher {
CanonicalRecipientAddress recipient,
int senderKeyId, int recipientKeyId,
PublicKey nextKey, int counter,
int messageVersion, int negotiatedVersion)
int messageVersion)
throws InvalidMessageException
{
try {
@@ -94,12 +95,16 @@ public class SessionCipher {
records.getSessionRecord().getNegotiatedSessionVersion());
}
SessionKey sessionKey = getSessionKey(masterSecret, Cipher.DECRYPT_MODE, messageVersion, localIdentityKey, records, recipientKeyId, senderKeyId);
SessionKey sessionKey = getSessionKey(masterSecret, Cipher.DECRYPT_MODE, messageVersion,
localIdentityKey, records, recipientKeyId, senderKeyId);
return new SessionCipherContext(records, sessionKey, senderKeyId,
recipientKeyId, nextKey, counter,
messageVersion, negotiatedVersion);
messageVersion);
} catch (InvalidKeyIdException e) {
throw new InvalidMessageException(e);
} catch (InvalidKeyException e) {
throw new InvalidMessageException(e);
}
}
@@ -125,7 +130,9 @@ public class SessionCipher {
{
Log.w("SessionCipher", "Decrypting message...");
try {
byte[] plaintextWithPadding = getPlaintext(decodedCiphertext, context.getSessionKey().getCipherKey(), context.getCounter());
byte[] plaintextWithPadding = getPlaintext(decodedCiphertext,
context.getSessionKey().getCipherKey(),
context.getCounter());
context.getRemoteKeyRecord().updateCurrentRemoteKey(context.getNextKey());
context.getRemoteKeyRecord().save();
@@ -134,7 +141,6 @@ public class SessionCipher {
context.getLocalKeyRecord().save();
context.getSessionRecord().setSessionKey(context.getSessionKey());
context.getSessionRecord().setSessionVersion(context.getNegotiatedVersion());
context.getSessionRecord().setPrekeyBundleRequired(false);
context.getSessionRecord().save();
@@ -175,7 +181,7 @@ public class SessionCipher {
throw new IllegalArgumentException("AES Not Supported!");
} catch (NoSuchPaddingException e) {
throw new IllegalArgumentException("NoPadding Not Supported!");
} catch (InvalidKeyException e) {
} catch (java.security.InvalidKeyException e) {
Log.w("SessionCipher", e);
throw new IllegalArgumentException("Invaid Key?");
} catch (InvalidAlgorithmParameterException e) {
@@ -189,7 +195,7 @@ public class SessionCipher {
IdentityKeyPair localIdentityKey,
KeyRecords records,
int localKeyId, int remoteKeyId)
throws InvalidKeyIdException
throws InvalidKeyIdException, InvalidKeyException
{
Log.w("SessionCipher", "Getting session key for local: " + localKeyId + " remote: " + remoteKeyId);
SessionKey sessionKey = records.getSessionRecord().getSessionKey(mode, localKeyId, remoteKeyId);
@@ -208,12 +214,12 @@ public class SessionCipher {
IdentityKeyPair localIdentityKey,
KeyRecords records,
int localKeyId, int remoteKeyId)
throws InvalidKeyIdException
throws InvalidKeyIdException, InvalidKeyException
{
KeyPair localKeyPair = records.getLocalKeyRecord().getKeyPairForId(localKeyId);
ECPublicKeyParameters remoteKey = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey();
IdentityKey remoteIdentityKey = records.getSessionRecord().getIdentityKey();
boolean isLowEnd = isLowEnd(records, localKeyId, remoteKeyId);
KeyPair localKeyPair = records.getLocalKeyRecord().getKeyPairForId(localKeyId);
ECPublicKey remoteKey = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey();
IdentityKey remoteIdentityKey = records.getSessionRecord().getIdentityKey();
boolean isLowEnd = isLowEnd(records, localKeyId, remoteKeyId);
isLowEnd = (mode == Cipher.ENCRYPT_MODE ? isLowEnd : !isLowEnd);
@@ -233,13 +239,10 @@ public class SessionCipher {
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();
ECPublicKey localPublic = records.getLocalKeyRecord().getKeyPairForId(localKeyId).getPublicKey().getKey();
ECPublicKey remotePublic = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey();
BigInteger local = localPublic.getQ().getX().toBigInteger();
BigInteger remote = remotePublic.getQ().getX().toBigInteger();
return local.compareTo(remote) < 0;
return localPublic.compareTo(remotePublic) < 0;
}
private boolean isInitiallyExchangedKeys(KeyRecords records, int localKeyId, int remoteKeyId)
@@ -297,7 +300,6 @@ public class SessionCipher {
private final PublicKey nextKey;
private final int counter;
private final int messageVersion;
private final int negotiatedVersion;
public SessionCipherContext(KeyRecords records,
SessionKey sessionKey,
@@ -305,8 +307,7 @@ public class SessionCipher {
int receiverKeyId,
PublicKey nextKey,
int counter,
int messageVersion,
int negotiatedVersion)
int messageVersion)
{
this.localKeyRecord = records.getLocalKeyRecord();
this.remoteKeyRecord = records.getRemoteKeyRecord();
@@ -317,7 +318,6 @@ public class SessionCipher {
this.nextKey = nextKey;
this.counter = counter;
this.messageVersion = messageVersion;
this.negotiatedVersion = negotiatedVersion;
}
public LocalKeyRecord getLocalKeyRecord() {
@@ -352,10 +352,6 @@ public class SessionCipher {
return recipientKeyId;
}
public int getNegotiatedVersion() {
return negotiatedVersion;
}
public int getMessageVersion() {
return messageVersion;
}

View File

@@ -1,10 +1,26 @@
/**
* Copyright (C) 2013 Open 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 <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto;
import android.util.Log;
import org.spongycastle.crypto.CipherParameters;
import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets;
import org.whispersystems.textsecure.crypto.kdf.HKDF;
import org.whispersystems.textsecure.crypto.kdf.KDF;
@@ -12,7 +28,6 @@ import org.whispersystems.textsecure.crypto.kdf.NKDF;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.util.Conversions;
import java.math.BigInteger;
import java.util.LinkedList;
import java.util.List;
@@ -21,34 +36,34 @@ public class SharedSecretCalculator {
public static DerivedSecrets calculateSharedSecret(boolean isLowEnd, KeyPair localKeyPair,
int localKeyId,
IdentityKeyPair localIdentityKeyPair,
ECPublicKeyParameters remoteKey,
ECPublicKey remoteKey,
int remoteKeyId,
IdentityKey remoteIdentityKey)
throws InvalidKeyException
{
Log.w("SharedSecretCalculator", "Calculating shared secret with cradle agreement...");
KDF kdf = new HKDF();
List<BigInteger> results = new LinkedList<BigInteger>();
Log.w("SharedSecretCalculator", "Calculating shared secret with 3DHE agreement...");
KDF kdf = new HKDF();
List<byte[]> results = new LinkedList<byte[]>();
if (isSmaller(localKeyPair.getPublicKey().getKey(), remoteKey)) {
results.add(calculateAgreement(localIdentityKeyPair.getPrivateKey(), remoteKey));
results.add(calculateAgreement(localKeyPair.getKeyPair().getPrivate(),
remoteIdentityKey.getPublicKeyParameters()));
results.add(Curve.calculateAgreement(remoteKey, localIdentityKeyPair.getPrivateKey()));
results.add(Curve.calculateAgreement(remoteIdentityKey.getPublicKey(),
localKeyPair.getPrivateKey()));
} else {
results.add(calculateAgreement(localKeyPair.getKeyPair().getPrivate(),
remoteIdentityKey.getPublicKeyParameters()));
results.add(calculateAgreement(localIdentityKeyPair.getPrivateKey(), remoteKey));
results.add(Curve.calculateAgreement(remoteIdentityKey.getPublicKey(),
localKeyPair.getPrivateKey()));
results.add(Curve.calculateAgreement(remoteKey, localIdentityKeyPair.getPrivateKey()));
}
results.add(calculateAgreement(localKeyPair.getKeyPair().getPrivate(), remoteKey));
results.add(Curve.calculateAgreement(remoteKey, localKeyPair.getPrivateKey()));
return kdf.deriveSecrets(results, isLowEnd, getInfo(localKeyId,remoteKeyId));
return kdf.deriveSecrets(results, isLowEnd, getInfo(localKeyId, remoteKeyId));
}
public static DerivedSecrets calculateSharedSecret(int messageVersion, boolean isLowEnd,
KeyPair localKeyPair, int localKeyId,
ECPublicKeyParameters remoteKey, int remoteKeyId)
ECPublicKey remoteKey, int remoteKeyId)
throws InvalidKeyException
{
Log.w("SharedSecretCalculator", "Calculating shared secret with standard agreement...");
KDF kdf;
@@ -58,8 +73,8 @@ public class SharedSecretCalculator {
Log.w("SharedSecretCalculator", "Using kdf: " + kdf);
List<BigInteger> results = new LinkedList<BigInteger>();
results.add(calculateAgreement(localKeyPair.getKeyPair().getPrivate(), remoteKey));
List<byte[]> results = new LinkedList<byte[]>();
results.add(Curve.calculateAgreement(remoteKey, localKeyPair.getPrivateKey()));
return kdf.deriveSecrets(results, isLowEnd, getInfo(localKeyId, remoteKeyId));
}
@@ -78,23 +93,10 @@ public class SharedSecretCalculator {
return info;
}
private static BigInteger calculateAgreement(CipherParameters privateKey,
ECPublicKeyParameters publicKey)
private static boolean isSmaller(ECPublicKey localPublic,
ECPublicKey remotePublic)
{
ECDHBasicAgreement agreement = new ECDHBasicAgreement();
agreement.init(privateKey);
return KeyUtil.calculateAgreement(agreement, publicKey);
}
private static boolean isSmaller(ECPublicKeyParameters localPublic,
ECPublicKeyParameters remotePublic)
{
BigInteger local = localPublic.getQ().getX().toBigInteger();
BigInteger remote = remotePublic.getQ().getX().toBigInteger();
return local.compareTo(remote) < 0;
return localPublic.compareTo(remotePublic) < 0;
}
}

View File

@@ -0,0 +1,85 @@
/**
* Copyright (C) 2013 Open 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 <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.ecc;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
public class Curve {
public static final int NIST_TYPE = 0x02;
private static final int NIST_TYPE2 = 0x03;
public static final int DJB_TYPE = 0x04;
public static ECKeyPair generateKeyPairForType(int keyType) {
if (keyType == DJB_TYPE) {
return Curve25519.generateKeyPair();
} else if (keyType == NIST_TYPE || keyType == NIST_TYPE2) {
return CurveP256.generateKeyPair();
} else {
throw new AssertionError("Bad key type: " + keyType);
}
}
public static ECKeyPair generateKeyPairForSession(int messageVersion) {
if (messageVersion >= CiphertextMessage.CURVE25519_INTRODUCED_VERSION) {
return generateKeyPairForType(DJB_TYPE);
} else {
return generateKeyPairForType(NIST_TYPE);
}
}
public static ECPublicKey decodePoint(byte[] bytes, int offset)
throws InvalidKeyException
{
int type = bytes[offset];
if (type == DJB_TYPE) {
return Curve25519.decodePoint(bytes, offset);
} else if (type == NIST_TYPE || type == NIST_TYPE2) {
return CurveP256.decodePoint(bytes, offset);
} else {
throw new InvalidKeyException("Unknown key type: " + type);
}
}
public static ECPrivateKey decodePrivatePoint(int type, byte[] bytes) {
if (type == DJB_TYPE) {
return new DjbECPrivateKey(bytes);
} else if (type == NIST_TYPE || type == NIST_TYPE2) {
return CurveP256.decodePrivatePoint(bytes);
} else {
throw new AssertionError("Bad key type: " + type);
}
}
public static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey)
throws InvalidKeyException
{
if (publicKey.getType() != privateKey.getType()) {
throw new InvalidKeyException("Public and private keys must be of the same type!");
}
if (publicKey.getType() == DJB_TYPE) {
return Curve25519.calculateAgreement(publicKey, privateKey);
} else if (publicKey.getType() == NIST_TYPE || publicKey.getType() == NIST_TYPE2) {
return CurveP256.calculateAgreement(publicKey, privateKey);
} else {
throw new InvalidKeyException("Unknown type: " + publicKey.getType());
}
}
}

View File

@@ -0,0 +1,75 @@
/**
* Copyright (C) 2013 Open 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 <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.ecc;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class Curve25519 {
static {
System.loadLibrary("curve25519");
try {
random = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
private static final SecureRandom random;
private static native byte[] calculateAgreement(byte[] ourPrivate, byte[] theirPublic);
private static native byte[] generatePublicKey(byte[] privateKey);
private static native byte[] generatePrivateKey(byte[] random);
public static ECKeyPair generateKeyPair() {
byte[] privateKey = generatePrivateKey();
byte[] publicKey = generatePublicKey(privateKey);
return new ECKeyPair(new DjbECPublicKey(publicKey), new DjbECPrivateKey(privateKey));
}
static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey) {
return calculateAgreement(((DjbECPrivateKey)privateKey).getPrivateKey(),
((DjbECPublicKey)publicKey).getPublicKey());
}
static ECPublicKey decodePoint(byte[] encoded, int offset)
throws InvalidKeyException
{
int type = encoded[offset] & 0xFF;
byte[] keyBytes = new byte[32];
System.arraycopy(encoded, offset+1, keyBytes, 0, keyBytes.length);
if (type != Curve.DJB_TYPE) {
throw new InvalidKeyException("Bad key type: " + type);
}
return new DjbECPublicKey(keyBytes);
}
private static byte[] generatePrivateKey() {
byte[] privateKey = new byte[32];
random.nextBytes(privateKey);
return generatePrivateKey(privateKey);
}
}

View File

@@ -0,0 +1,122 @@
/**
* Copyright (C) 2013 Open 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 <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.ecc;
import android.util.Log;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECFieldElement;
import org.spongycastle.math.ec.ECPoint;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class CurveP256 {
private static final BigInteger q = new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16);
private static final BigInteger a = new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16);
private static final BigInteger b = new BigInteger("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", 16);
private static final BigInteger n = new BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16);
private static final ECFieldElement x = new ECFieldElement.Fp(q, new BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16));
private static final ECFieldElement y = new ECFieldElement.Fp(q, new BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16));
private static final ECCurve curve = new ECCurve.Fp(q, a, b);
private static final ECPoint g = new ECPoint.Fp(curve, x, y, true);
private static final ECDomainParameters domainParameters = new ECDomainParameters(curve, g, n);
public static final int P256_POINT_SIZE = 33;
static byte[] encodePoint(ECPoint point) {
synchronized (curve) {
return point.getEncoded();
}
}
static ECPublicKey decodePoint(byte[] encoded, int offset)
throws InvalidKeyException
{
byte[] pointBytes = new byte[P256_POINT_SIZE];
System.arraycopy(encoded, offset, pointBytes, 0, pointBytes.length);
synchronized (curve) {
ECPoint Q;
try {
Q = curve.decodePoint(pointBytes);
} catch (RuntimeException re) {
throw new InvalidKeyException(re);
}
return new NistECPublicKey(new ECPublicKeyParameters(Q, domainParameters));
}
}
static ECPrivateKey decodePrivatePoint(byte[] encoded) {
BigInteger d = new BigInteger(encoded);
return new NistECPrivateKey(new ECPrivateKeyParameters(d, domainParameters));
}
static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey) {
ECDHBasicAgreement agreement = new ECDHBasicAgreement();
agreement.init(((NistECPrivateKey)privateKey).getParameters());
synchronized (curve) {
return agreement.calculateAgreement(((NistECPublicKey)publicKey).getParameters()).toByteArray();
}
}
public static ECKeyPair generateKeyPair() {
try {
synchronized (curve) {
ECKeyGenerationParameters keyParamters = new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG"));
ECKeyPairGenerator generator = new ECKeyPairGenerator();
generator.init(keyParamters);
AsymmetricCipherKeyPair keyPair = generator.generateKeyPair();
keyPair = cloneKeyPairWithPointCompression(keyPair);
return new ECKeyPair(new NistECPublicKey((ECPublicKeyParameters)keyPair.getPublic()),
new NistECPrivateKey((ECPrivateKeyParameters)keyPair.getPrivate()));
}
} catch (NoSuchAlgorithmException nsae) {
Log.w("CurveP256", nsae);
throw new AssertionError(nsae);
}
}
// This is dumb, but the ECPublicKeys that the generator makes by default don't have point compression
// turned on, and there's no setter. Great.
private static AsymmetricCipherKeyPair cloneKeyPairWithPointCompression(AsymmetricCipherKeyPair keyPair) {
ECPublicKeyParameters publicKey = (ECPublicKeyParameters)keyPair.getPublic();
ECPoint q = publicKey.getQ();
return new AsymmetricCipherKeyPair(new ECPublicKeyParameters(new ECPoint.Fp(q.getCurve(), q.getX(), q.getY(), true),
publicKey.getParameters()), keyPair.getPrivate());
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (C) 2013 Open 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 <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.ecc;
public class DjbECPrivateKey implements ECPrivateKey {
private final byte[] privateKey;
DjbECPrivateKey(byte[] privateKey) {
this.privateKey = privateKey;
}
@Override
public byte[] serialize() {
return privateKey;
}
@Override
public int getType() {
return Curve.DJB_TYPE;
}
public byte[] getPrivateKey() {
return privateKey;
}
}

View File

@@ -0,0 +1,66 @@
/**
* Copyright (C) 2013 Open 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 <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.ecc;
import org.whispersystems.textsecure.util.Util;
import java.math.BigInteger;
import java.util.Arrays;
public class DjbECPublicKey implements ECPublicKey {
private final byte[] publicKey;
DjbECPublicKey(byte[] publicKey) {
this.publicKey = publicKey;
}
@Override
public byte[] serialize() {
byte[] type = {Curve.DJB_TYPE};
return Util.combine(type, publicKey);
}
@Override
public int getType() {
return Curve.DJB_TYPE;
}
@Override
public boolean equals(Object other) {
if (other == null) return false;
if (!(other instanceof DjbECPublicKey)) return false;
DjbECPublicKey that = (DjbECPublicKey)other;
return Arrays.equals(this.publicKey, that.publicKey);
}
@Override
public int hashCode() {
return Arrays.hashCode(publicKey);
}
@Override
public int compareTo(ECPublicKey another) {
return new BigInteger(publicKey).compareTo(new BigInteger(((DjbECPublicKey)another).publicKey));
}
public byte[] getPublicKey() {
return publicKey;
}
}

View File

@@ -0,0 +1,38 @@
/**
* Copyright (C) 2013 Open 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 <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.ecc;
public class ECKeyPair {
private final ECPublicKey publicKey;
private final ECPrivateKey privateKey;
public ECKeyPair(ECPublicKey publicKey, ECPrivateKey privateKey) {
this.publicKey = publicKey;
this.privateKey = privateKey;
}
public ECPublicKey getPublicKey() {
return publicKey;
}
public ECPrivateKey getPrivateKey() {
return privateKey;
}
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright (C) 2013 Open 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 <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.ecc;
public interface ECPrivateKey {
public byte[] serialize();
public int getType();
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (C) 2013 Open 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 <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.ecc;
public interface ECPublicKey extends Comparable<ECPublicKey> {
public static final int KEY_SIZE = 33;
public byte[] serialize();
public int getType();
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (C) 2013 Open 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 <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.ecc;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
public class NistECPrivateKey implements ECPrivateKey {
private final ECPrivateKeyParameters privateKey;
public NistECPrivateKey(ECPrivateKeyParameters privateKey) {
this.privateKey = privateKey;
}
@Override
public byte[] serialize() {
return privateKey.getD().toByteArray();
}
@Override
public int getType() {
return Curve.NIST_TYPE;
}
public ECPrivateKeyParameters getParameters() {
return privateKey;
}
}

View File

@@ -0,0 +1,63 @@
/**
* Copyright (C) 2013 Open 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 <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.ecc;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
public class NistECPublicKey implements ECPublicKey {
private final ECPublicKeyParameters publicKey;
NistECPublicKey(ECPublicKeyParameters publicKey) {
this.publicKey = publicKey;
}
@Override
public byte[] serialize() {
return CurveP256.encodePoint(publicKey.getQ());
}
@Override
public int getType() {
return Curve.NIST_TYPE;
}
@Override
public boolean equals(Object other) {
if (other == null) return false;
if (!(other instanceof NistECPublicKey)) return false;
NistECPublicKey that = (NistECPublicKey)other;
return publicKey.getQ().equals(that.publicKey.getQ());
}
@Override
public int hashCode() {
return publicKey.getQ().hashCode();
}
@Override
public int compareTo(ECPublicKey another) {
return publicKey.getQ().getX().toBigInteger()
.compareTo(((NistECPublicKey) another).publicKey.getQ().getX().toBigInteger());
}
public ECPublicKeyParameters getParameters() {
return publicKey;
}
}

View File

@@ -1,3 +1,20 @@
/**
* Copyright (C) 2013 Open 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 <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.kdf;
import javax.crypto.spec.SecretKeySpec;

View File

@@ -1,3 +1,20 @@
/**
* Copyright (C) 2013 Open 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 <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.kdf;
import java.io.ByteArrayOutputStream;
@@ -18,7 +35,7 @@ public class HKDF extends KDF {
private static final int MAC_KEYS_OFFSET = 32;
@Override
public DerivedSecrets deriveSecrets(List<BigInteger> sharedSecret,
public DerivedSecrets deriveSecrets(List<byte[]> sharedSecret,
boolean isLowEnd, byte[] info)
{
byte[] inputKeyMaterial = concatenateSharedSecrets(sharedSecret);

View File

@@ -1,33 +1,45 @@
/**
* Copyright (C) 2013 Open 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 <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.kdf;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.LinkedList;
import java.util.List;
public abstract class KDF {
public abstract DerivedSecrets deriveSecrets(List<BigInteger> sharedSecret,
public abstract DerivedSecrets deriveSecrets(List<byte[]> sharedSecret,
boolean isLowEnd, byte[] info);
protected byte[] concatenateSharedSecrets(List<BigInteger> sharedSecrets) {
int totalByteSize = 0;
List<byte[]> byteValues = new LinkedList<byte[]>();
protected byte[] concatenateSharedSecrets(List<byte[]> sharedSecrets) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (BigInteger sharedSecret : sharedSecrets) {
byte[] byteValue = sharedSecret.toByteArray();
totalByteSize += byteValue.length;
byteValues.add(byteValue);
for (byte[] sharedSecret : sharedSecrets) {
baos.write(sharedSecret);
}
return baos.toByteArray();
} catch (IOException e) {
throw new AssertionError(e);
}
byte[] combined = new byte[totalByteSize];
int offset = 0;
for (byte[] byteValue : byteValues) {
System.arraycopy(byteValue, 0, combined, offset, byteValue.length);
offset += byteValue.length;
}
return combined;
}
}

View File

@@ -1,10 +1,26 @@
/**
* Copyright (C) 2013 Open 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 <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.kdf;
import android.util.Log;
import org.whispersystems.textsecure.util.Conversions;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
@@ -14,7 +30,7 @@ import javax.crypto.spec.SecretKeySpec;
public class NKDF extends KDF {
@Override
public DerivedSecrets deriveSecrets(List<BigInteger> sharedSecret,
public DerivedSecrets deriveSecrets(List<byte[]> sharedSecret,
boolean isLowEnd, byte[] info)
{
SecretKeySpec cipherKey = deriveCipherSecret(isLowEnd, sharedSecret);
@@ -23,7 +39,7 @@ public class NKDF extends KDF {
return new DerivedSecrets(cipherKey, macKey);
}
private SecretKeySpec deriveCipherSecret(boolean isLowEnd, List<BigInteger> sharedSecret) {
private SecretKeySpec deriveCipherSecret(boolean isLowEnd, List<byte[]> sharedSecret) {
byte[] sharedSecretBytes = concatenateSharedSecrets(sharedSecret);
byte[] derivedBytes = deriveBytes(sharedSecretBytes, 16 * 2);
byte[] cipherSecret = new byte[16];

View File

@@ -9,8 +9,9 @@ import org.whispersystems.textsecure.util.Conversions;
public class CiphertextMessage {
public static final int SUPPORTED_VERSION = 2;
public static final int DHE3_INTRODUCED_VERSION = 2;
public static final int SUPPORTED_VERSION = 2;
public static final int DHE3_INTRODUCED_VERSION = 2;
public static final int CURVE25519_INTRODUCED_VERSION = 2;
static final int VERSION_LENGTH = 1;
private static final int SENDER_KEY_ID_LENGTH = 3;

View File

@@ -77,7 +77,9 @@ public class PreKeyBundleMessage {
messageBytes[VERSION_OFFSET] = bundledMessageBytes[VERSION_OFFSET];
System.arraycopy(identityKeyBytes, 0, messageBytes, IDENTITY_KEY_OFFSET, identityKeyBytes.length);
System.arraycopy(bundledMessageBytes, VERSION_OFFSET+VERSION_LENGTH, messageBytes, IDENTITY_KEY_OFFSET+IDENTITY_KEY_LENGTH, bundledMessageBytes.length-VERSION_LENGTH);
System.arraycopy(bundledMessageBytes, VERSION_OFFSET+VERSION_LENGTH,
messageBytes, IDENTITY_KEY_OFFSET+IDENTITY_KEY_LENGTH,
bundledMessageBytes.length-VERSION_LENGTH);
}
public byte[] serialize() {

View File

@@ -133,6 +133,7 @@ public class PushServiceSocket {
PreKeyEntity entity = new PreKeyEntity(record.getId(),
record.getKeyPair().getPublicKey(),
identityKey);
entities.add(entity);
}
@@ -140,7 +141,9 @@ public class PushServiceSocket {
lastResortKey.getKeyPair().getPublicKey(),
identityKey);
makeRequest(String.format(PREKEY_PATH, ""), "PUT", PreKeyList.toJson(new PreKeyList(lastResortEntity, entities)));
makeRequest(String.format(PREKEY_PATH, ""), "PUT",
PreKeyList.toJson(new PreKeyList(lastResortEntity, entities)));
}
public PreKeyEntity getPreKey(PushDestination destination) throws IOException {

View File

@@ -1,5 +1,6 @@
/**
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013 Open 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
@@ -21,9 +22,9 @@ import android.util.Log;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.KeyPair;
import org.whispersystems.textsecure.crypto.KeyUtil;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.util.Medium;
import java.io.FileInputStream;
@@ -65,9 +66,12 @@ public class LocalKeyRecord extends Record {
public void advanceKeyIfNecessary(int keyId) {
Log.w("LocalKeyRecord", "Remote client acknowledges receiving key id: " + keyId);
if (keyId == localNextKeyPair.getId()) {
int keyType = this.localNextKeyPair.getPublicKey().getType();
this.localCurrentKeyPair = this.localNextKeyPair;
this.localNextKeyPair = new KeyPair((this.localNextKeyPair.getId()+1) % Medium.MAX_VALUE,
KeyUtil.generateKeyPair(), masterSecret);
Curve.generateKeyPairForType(keyType),
masterSecret);
}
}