mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-19 06:09:55 +01:00
Changed the root package from net.sf.briar to org.briarproject.
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
import org.briarproject.api.crypto.AuthenticatedCipher;
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
|
||||
import org.spongycastle.crypto.DataLengthException;
|
||||
import org.spongycastle.crypto.InvalidCipherTextException;
|
||||
import org.spongycastle.crypto.modes.AEADBlockCipher;
|
||||
import org.spongycastle.crypto.params.AEADParameters;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
class AuthenticatedCipherImpl implements AuthenticatedCipher {
|
||||
|
||||
private final AEADBlockCipher cipher;
|
||||
private final int macLength;
|
||||
|
||||
AuthenticatedCipherImpl(AEADBlockCipher cipher, int macLength) {
|
||||
this.cipher = cipher;
|
||||
this.macLength = macLength;
|
||||
}
|
||||
|
||||
public int doFinal(byte[] input, int inputOff, int len, byte[] output,
|
||||
int outputOff) throws GeneralSecurityException {
|
||||
int processed = 0;
|
||||
if(len != 0) {
|
||||
processed = cipher.processBytes(input, inputOff, len, output,
|
||||
outputOff);
|
||||
}
|
||||
try {
|
||||
return processed + cipher.doFinal(output, outputOff + processed);
|
||||
} catch(DataLengthException e) {
|
||||
throw new GeneralSecurityException(e.getMessage());
|
||||
} catch(InvalidCipherTextException e) {
|
||||
throw new GeneralSecurityException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void init(int opmode, SecretKey key, byte[] iv, byte[] aad)
|
||||
throws GeneralSecurityException {
|
||||
KeyParameter k = new KeyParameter(key.getEncoded());
|
||||
AEADParameters params = new AEADParameters(k, macLength * 8, iv, aad);
|
||||
try {
|
||||
switch(opmode) {
|
||||
case Cipher.ENCRYPT_MODE:
|
||||
case Cipher.WRAP_MODE:
|
||||
cipher.init(true, params);
|
||||
break;
|
||||
case Cipher.DECRYPT_MODE:
|
||||
case Cipher.UNWRAP_MODE:
|
||||
cipher.init(false, params);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
} catch(IllegalArgumentException e) {
|
||||
throw new GeneralSecurityException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public int getMacLength() {
|
||||
return macLength;
|
||||
}
|
||||
|
||||
public int getBlockSize() {
|
||||
return cipher.getUnderlyingCipher().getBlockSize();
|
||||
}
|
||||
}
|
||||
526
briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java
Normal file
526
briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java
Normal file
@@ -0,0 +1,526 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static javax.crypto.Cipher.DECRYPT_MODE;
|
||||
import static javax.crypto.Cipher.ENCRYPT_MODE;
|
||||
import static org.briarproject.api.invitation.InvitationConstants.CODE_BITS;
|
||||
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
|
||||
import static org.briarproject.crypto.EllipticCurveConstants.P;
|
||||
import static org.briarproject.crypto.EllipticCurveConstants.PARAMETERS;
|
||||
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.briarproject.api.crypto.AuthenticatedCipher;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.KeyPair;
|
||||
import org.briarproject.api.crypto.KeyParser;
|
||||
import org.briarproject.api.crypto.MessageDigest;
|
||||
import org.briarproject.api.crypto.PrivateKey;
|
||||
import org.briarproject.api.crypto.PseudoRandom;
|
||||
import org.briarproject.api.crypto.PublicKey;
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
import org.briarproject.api.crypto.Signature;
|
||||
import org.briarproject.util.ByteUtils;
|
||||
|
||||
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.spongycastle.crypto.BlockCipher;
|
||||
import org.spongycastle.crypto.CipherParameters;
|
||||
import org.spongycastle.crypto.Mac;
|
||||
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||
import org.spongycastle.crypto.digests.SHA384Digest;
|
||||
import org.spongycastle.crypto.engines.AESLightEngine;
|
||||
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
|
||||
import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator;
|
||||
import org.spongycastle.crypto.macs.HMac;
|
||||
import org.spongycastle.crypto.modes.AEADBlockCipher;
|
||||
import org.spongycastle.crypto.modes.GCMBlockCipher;
|
||||
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
|
||||
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
import org.spongycastle.util.Strings;
|
||||
|
||||
class CryptoComponentImpl implements CryptoComponent {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(CryptoComponentImpl.class.getName());
|
||||
|
||||
private static final int CIPHER_KEY_BYTES = 32; // 256 bits
|
||||
private static final int AGREEMENT_KEY_PAIR_BITS = 384;
|
||||
private static final int SIGNATURE_KEY_PAIR_BITS = 384;
|
||||
private static final int MAC_BYTES = 16; // 128 bits
|
||||
private static final int STORAGE_IV_BYTES = 16; // 128 bits
|
||||
private static final int PBKDF_SALT_BYTES = 16; // 128 bits
|
||||
private static final int PBKDF_TARGET_MILLIS = 500;
|
||||
private static final int PBKDF_SAMPLES = 30;
|
||||
|
||||
// Labels for secret derivation
|
||||
private static final byte[] MASTER = { 'M', 'A', 'S', 'T', 'E', 'R', '\0' };
|
||||
private static final byte[] SALT = { 'S', 'A', 'L', 'T', '\0' };
|
||||
private static final byte[] FIRST = { 'F', 'I', 'R', 'S', 'T', '\0' };
|
||||
private static final byte[] ROTATE = { 'R', 'O', 'T', 'A', 'T', 'E', '\0' };
|
||||
// Label for confirmation code derivation
|
||||
private static final byte[] CODE = { 'C', 'O', 'D', 'E', '\0' };
|
||||
// Label for invitation nonce derivation
|
||||
private static final byte[] NONCE = { 'N', 'O', 'N', 'C', 'E', '\0' };
|
||||
// Labels for key derivation
|
||||
private static final byte[] A_TAG = { 'A', '_', 'T', 'A', 'G', '\0' };
|
||||
private static final byte[] B_TAG = { 'B', '_', 'T', 'A', 'G', '\0' };
|
||||
private static final byte[] A_FRAME_A =
|
||||
{ 'A', '_', 'F', 'R', 'A', 'M', 'E', '_', 'A', '\0' };
|
||||
private static final byte[] A_FRAME_B =
|
||||
{ 'A', '_', 'F', 'R', 'A', 'M', 'E', '_', 'B', '\0' };
|
||||
private static final byte[] B_FRAME_A =
|
||||
{ 'B', '_', 'F', 'R', 'A', 'M', 'E', '_', 'A', '\0' };
|
||||
private static final byte[] B_FRAME_B =
|
||||
{ 'B', '_', 'F', 'R', 'A', 'M', 'E', '_', 'B', '\0' };
|
||||
// Blank secret for argument validation
|
||||
private static final byte[] BLANK_SECRET = new byte[CIPHER_KEY_BYTES];
|
||||
|
||||
private final KeyParser agreementKeyParser, signatureKeyParser;
|
||||
private final SecureRandom secureRandom;
|
||||
private final ECKeyPairGenerator agreementKeyPairGenerator;
|
||||
private final ECKeyPairGenerator signatureKeyPairGenerator;
|
||||
|
||||
CryptoComponentImpl() {
|
||||
agreementKeyParser = new Sec1KeyParser(PARAMETERS, P,
|
||||
AGREEMENT_KEY_PAIR_BITS);
|
||||
signatureKeyParser = new Sec1KeyParser(PARAMETERS, P,
|
||||
SIGNATURE_KEY_PAIR_BITS);
|
||||
secureRandom = new SecureRandom();
|
||||
ECKeyGenerationParameters params = new ECKeyGenerationParameters(
|
||||
PARAMETERS, secureRandom);
|
||||
agreementKeyPairGenerator = new ECKeyPairGenerator();
|
||||
agreementKeyPairGenerator.init(params);
|
||||
signatureKeyPairGenerator = new ECKeyPairGenerator();
|
||||
signatureKeyPairGenerator.init(params);
|
||||
}
|
||||
|
||||
public SecretKey generateSecretKey() {
|
||||
byte[] b = new byte[CIPHER_KEY_BYTES];
|
||||
secureRandom.nextBytes(b);
|
||||
return new SecretKeyImpl(b);
|
||||
}
|
||||
|
||||
public MessageDigest getMessageDigest() {
|
||||
return new DoubleDigest(new SHA384Digest());
|
||||
}
|
||||
|
||||
public PseudoRandom getPseudoRandom(int seed1, int seed2) {
|
||||
return new PseudoRandomImpl(getMessageDigest(), seed1, seed2);
|
||||
}
|
||||
|
||||
public SecureRandom getSecureRandom() {
|
||||
return secureRandom;
|
||||
}
|
||||
|
||||
public Signature getSignature() {
|
||||
return new SignatureImpl(secureRandom);
|
||||
}
|
||||
|
||||
public KeyPair generateAgreementKeyPair() {
|
||||
AsymmetricCipherKeyPair keyPair =
|
||||
agreementKeyPairGenerator.generateKeyPair();
|
||||
// Return a wrapper that uses the SEC 1 encoding
|
||||
ECPublicKeyParameters ecPublicKey =
|
||||
(ECPublicKeyParameters) keyPair.getPublic();
|
||||
PublicKey publicKey = new Sec1PublicKey(ecPublicKey,
|
||||
AGREEMENT_KEY_PAIR_BITS);
|
||||
ECPrivateKeyParameters ecPrivateKey =
|
||||
(ECPrivateKeyParameters) keyPair.getPrivate();
|
||||
PrivateKey privateKey = new Sec1PrivateKey(ecPrivateKey,
|
||||
AGREEMENT_KEY_PAIR_BITS);
|
||||
return new KeyPair(publicKey, privateKey);
|
||||
}
|
||||
|
||||
public KeyParser getAgreementKeyParser() {
|
||||
return agreementKeyParser;
|
||||
}
|
||||
|
||||
public KeyPair generateSignatureKeyPair() {
|
||||
AsymmetricCipherKeyPair keyPair =
|
||||
signatureKeyPairGenerator.generateKeyPair();
|
||||
// Return a wrapper that uses the SEC 1 encoding
|
||||
ECPublicKeyParameters ecPublicKey =
|
||||
(ECPublicKeyParameters) keyPair.getPublic();
|
||||
PublicKey publicKey = new Sec1PublicKey(ecPublicKey,
|
||||
SIGNATURE_KEY_PAIR_BITS);
|
||||
ECPrivateKeyParameters ecPrivateKey =
|
||||
(ECPrivateKeyParameters) keyPair.getPrivate();
|
||||
PrivateKey privateKey = new Sec1PrivateKey(ecPrivateKey,
|
||||
SIGNATURE_KEY_PAIR_BITS);
|
||||
return new KeyPair(publicKey, privateKey);
|
||||
}
|
||||
|
||||
public KeyParser getSignatureKeyParser() {
|
||||
return signatureKeyParser;
|
||||
}
|
||||
|
||||
public int generateInvitationCode() {
|
||||
int codeBytes = (int) Math.ceil(CODE_BITS / 8.0);
|
||||
byte[] random = new byte[codeBytes];
|
||||
secureRandom.nextBytes(random);
|
||||
return ByteUtils.readUint(random, CODE_BITS);
|
||||
}
|
||||
|
||||
public int[] deriveConfirmationCodes(byte[] secret) {
|
||||
if(secret.length != CIPHER_KEY_BYTES)
|
||||
throw new IllegalArgumentException();
|
||||
if(Arrays.equals(secret, BLANK_SECRET))
|
||||
throw new IllegalArgumentException();
|
||||
byte[] alice = counterModeKdf(secret, CODE, 0);
|
||||
byte[] bob = counterModeKdf(secret, CODE, 1);
|
||||
int[] codes = new int[2];
|
||||
codes[0] = ByteUtils.readUint(alice, CODE_BITS);
|
||||
codes[1] = ByteUtils.readUint(bob, CODE_BITS);
|
||||
ByteUtils.erase(alice);
|
||||
ByteUtils.erase(bob);
|
||||
return codes;
|
||||
}
|
||||
|
||||
public byte[][] deriveInvitationNonces(byte[] secret) {
|
||||
if(secret.length != CIPHER_KEY_BYTES)
|
||||
throw new IllegalArgumentException();
|
||||
if(Arrays.equals(secret, BLANK_SECRET))
|
||||
throw new IllegalArgumentException();
|
||||
byte[] alice = counterModeKdf(secret, NONCE, 0);
|
||||
byte[] bob = counterModeKdf(secret, NONCE, 1);
|
||||
return new byte[][] { alice, bob };
|
||||
}
|
||||
|
||||
public byte[] deriveMasterSecret(byte[] theirPublicKey,
|
||||
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
|
||||
MessageDigest messageDigest = getMessageDigest();
|
||||
byte[] ourPublicKey = ourKeyPair.getPublic().getEncoded();
|
||||
byte[] ourHash = messageDigest.digest(ourPublicKey);
|
||||
byte[] theirHash = messageDigest.digest(theirPublicKey);
|
||||
byte[] aliceInfo, bobInfo;
|
||||
if(alice) {
|
||||
aliceInfo = ourHash;
|
||||
bobInfo = theirHash;
|
||||
} else {
|
||||
aliceInfo = theirHash;
|
||||
bobInfo = ourHash;
|
||||
}
|
||||
PrivateKey ourPriv = ourKeyPair.getPrivate();
|
||||
PublicKey theirPub = agreementKeyParser.parsePublicKey(theirPublicKey);
|
||||
// The raw secret comes from the key agreement algorithm
|
||||
byte[] raw = deriveSharedSecret(ourPriv, theirPub);
|
||||
// Derive the cooked secret from the raw secret using the
|
||||
// concatenation KDF
|
||||
byte[] cooked = concatenationKdf(raw, MASTER, aliceInfo, bobInfo);
|
||||
ByteUtils.erase(raw);
|
||||
return cooked;
|
||||
}
|
||||
|
||||
// Package access for testing
|
||||
byte[] deriveSharedSecret(PrivateKey priv, PublicKey pub)
|
||||
throws GeneralSecurityException {
|
||||
if(!(priv instanceof Sec1PrivateKey))
|
||||
throw new IllegalArgumentException();
|
||||
if(!(pub instanceof Sec1PublicKey))
|
||||
throw new IllegalArgumentException();
|
||||
ECPrivateKeyParameters ecPriv = ((Sec1PrivateKey) priv).getKey();
|
||||
ECPublicKeyParameters ecPub = ((Sec1PublicKey) pub).getKey();
|
||||
ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();
|
||||
agreement.init(ecPriv);
|
||||
// FIXME: Should we use another format for the shared secret?
|
||||
return agreement.calculateAgreement(ecPub).toByteArray();
|
||||
}
|
||||
|
||||
public byte[] deriveGroupSalt(byte[] secret) {
|
||||
if(secret.length != CIPHER_KEY_BYTES)
|
||||
throw new IllegalArgumentException();
|
||||
if(Arrays.equals(secret, BLANK_SECRET))
|
||||
throw new IllegalArgumentException();
|
||||
return counterModeKdf(secret, SALT, 0);
|
||||
}
|
||||
|
||||
public byte[] deriveInitialSecret(byte[] secret, int transportIndex) {
|
||||
if(secret.length != CIPHER_KEY_BYTES)
|
||||
throw new IllegalArgumentException();
|
||||
if(Arrays.equals(secret, BLANK_SECRET))
|
||||
throw new IllegalArgumentException();
|
||||
if(transportIndex < 0) throw new IllegalArgumentException();
|
||||
return counterModeKdf(secret, FIRST, transportIndex);
|
||||
}
|
||||
|
||||
public byte[] deriveNextSecret(byte[] secret, long period) {
|
||||
if(secret.length != CIPHER_KEY_BYTES)
|
||||
throw new IllegalArgumentException();
|
||||
if(Arrays.equals(secret, BLANK_SECRET))
|
||||
throw new IllegalArgumentException();
|
||||
if(period < 0 || period > MAX_32_BIT_UNSIGNED)
|
||||
throw new IllegalArgumentException();
|
||||
return counterModeKdf(secret, ROTATE, period);
|
||||
}
|
||||
|
||||
public SecretKey deriveTagKey(byte[] secret, boolean alice) {
|
||||
if(secret.length != CIPHER_KEY_BYTES)
|
||||
throw new IllegalArgumentException();
|
||||
if(Arrays.equals(secret, BLANK_SECRET))
|
||||
throw new IllegalArgumentException();
|
||||
if(alice) return deriveKey(secret, A_TAG, 0);
|
||||
else return deriveKey(secret, B_TAG, 0);
|
||||
}
|
||||
|
||||
public SecretKey deriveFrameKey(byte[] secret, long connection,
|
||||
boolean alice, boolean initiator) {
|
||||
if(secret.length != CIPHER_KEY_BYTES)
|
||||
throw new IllegalArgumentException();
|
||||
if(Arrays.equals(secret, BLANK_SECRET))
|
||||
throw new IllegalArgumentException();
|
||||
if(connection < 0 || connection > MAX_32_BIT_UNSIGNED)
|
||||
throw new IllegalArgumentException();
|
||||
if(alice) {
|
||||
if(initiator) return deriveKey(secret, A_FRAME_A, connection);
|
||||
else return deriveKey(secret, A_FRAME_B, connection);
|
||||
} else {
|
||||
if(initiator) return deriveKey(secret, B_FRAME_A, connection);
|
||||
else return deriveKey(secret, B_FRAME_B, connection);
|
||||
}
|
||||
}
|
||||
|
||||
private SecretKey deriveKey(byte[] secret, byte[] label, long context) {
|
||||
if(secret.length != CIPHER_KEY_BYTES)
|
||||
throw new IllegalArgumentException();
|
||||
if(Arrays.equals(secret, BLANK_SECRET))
|
||||
throw new IllegalArgumentException();
|
||||
byte[] key = counterModeKdf(secret, label, context);
|
||||
return new SecretKeyImpl(key);
|
||||
}
|
||||
|
||||
public AuthenticatedCipher getFrameCipher() {
|
||||
AEADBlockCipher cipher = new GCMBlockCipher(new AESLightEngine());
|
||||
return new AuthenticatedCipherImpl(cipher, MAC_BYTES);
|
||||
}
|
||||
|
||||
public void encodeTag(byte[] tag, SecretKey tagKey, long connection) {
|
||||
if(tag.length < TAG_LENGTH) throw new IllegalArgumentException();
|
||||
if(connection < 0 || connection > MAX_32_BIT_UNSIGNED)
|
||||
throw new IllegalArgumentException();
|
||||
for(int i = 0; i < TAG_LENGTH; i++) tag[i] = 0;
|
||||
ByteUtils.writeUint32(connection, tag, 0);
|
||||
BlockCipher cipher = new AESLightEngine();
|
||||
assert cipher.getBlockSize() == TAG_LENGTH;
|
||||
KeyParameter k = new KeyParameter(tagKey.getEncoded());
|
||||
cipher.init(true, k);
|
||||
cipher.processBlock(tag, 0, tag, 0);
|
||||
ByteUtils.erase(k.getKey());
|
||||
}
|
||||
|
||||
public byte[] encryptWithPassword(byte[] input, char[] password) {
|
||||
// Generate a random salt
|
||||
byte[] salt = new byte[PBKDF_SALT_BYTES];
|
||||
secureRandom.nextBytes(salt);
|
||||
// Calibrate the KDF
|
||||
int iterations = chooseIterationCount(PBKDF_TARGET_MILLIS);
|
||||
// Derive the key from the password
|
||||
byte[] keyBytes = pbkdf2(password, salt, iterations);
|
||||
SecretKey key = new SecretKeyImpl(keyBytes);
|
||||
// Generate a random IV
|
||||
byte[] iv = new byte[STORAGE_IV_BYTES];
|
||||
secureRandom.nextBytes(iv);
|
||||
// The output contains the salt, iterations, IV, ciphertext and MAC
|
||||
int outputLen = salt.length + 4 + iv.length + input.length + MAC_BYTES;
|
||||
byte[] output = new byte[outputLen];
|
||||
System.arraycopy(salt, 0, output, 0, salt.length);
|
||||
ByteUtils.writeUint32(iterations, output, salt.length);
|
||||
System.arraycopy(iv, 0, output, salt.length + 4, iv.length);
|
||||
// Initialise the cipher and encrypt the plaintext
|
||||
try {
|
||||
AEADBlockCipher c = new GCMBlockCipher(new AESLightEngine());
|
||||
AuthenticatedCipher cipher = new AuthenticatedCipherImpl(c,
|
||||
MAC_BYTES);
|
||||
cipher.init(ENCRYPT_MODE, key, iv, null);
|
||||
int outputOff = salt.length + 4 + iv.length;
|
||||
cipher.doFinal(input, 0, input.length, output, outputOff);
|
||||
return output;
|
||||
} catch(GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
key.erase();
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] decryptWithPassword(byte[] input, char[] password) {
|
||||
// The input contains the salt, iterations, IV, ciphertext and MAC
|
||||
if(input.length < PBKDF_SALT_BYTES + 4 + STORAGE_IV_BYTES + MAC_BYTES)
|
||||
return null; // Invalid
|
||||
byte[] salt = new byte[PBKDF_SALT_BYTES];
|
||||
System.arraycopy(input, 0, salt, 0, salt.length);
|
||||
long iterations = ByteUtils.readUint32(input, salt.length);
|
||||
if(iterations < 0 || iterations > Integer.MAX_VALUE)
|
||||
return null; // Invalid
|
||||
byte[] iv = new byte[STORAGE_IV_BYTES];
|
||||
System.arraycopy(input, salt.length + 4, iv, 0, iv.length);
|
||||
// Derive the key from the password
|
||||
byte[] keyBytes = pbkdf2(password, salt, (int) iterations);
|
||||
SecretKey key = new SecretKeyImpl(keyBytes);
|
||||
// Initialise the cipher
|
||||
AuthenticatedCipher cipher;
|
||||
try {
|
||||
AEADBlockCipher c = new GCMBlockCipher(new AESLightEngine());
|
||||
cipher = new AuthenticatedCipherImpl(c, MAC_BYTES);
|
||||
cipher.init(DECRYPT_MODE, key, iv, null);
|
||||
} catch(GeneralSecurityException e) {
|
||||
key.erase();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
// Try to decrypt the ciphertext (may be invalid)
|
||||
try {
|
||||
int inputOff = salt.length + 4 + iv.length;
|
||||
int inputLen = input.length - inputOff;
|
||||
byte[] output = new byte[inputLen - MAC_BYTES];
|
||||
cipher.doFinal(input, inputOff, inputLen, output, 0);
|
||||
return output;
|
||||
} catch(GeneralSecurityException e) {
|
||||
return null; // Invalid
|
||||
} finally {
|
||||
key.erase();
|
||||
}
|
||||
}
|
||||
|
||||
// Key derivation function based on a hash function - see NIST SP 800-56A,
|
||||
// section 5.8
|
||||
private byte[] concatenationKdf(byte[] rawSecret, byte[] label,
|
||||
byte[] initiatorInfo, byte[] responderInfo) {
|
||||
// The output of the hash function must be long enough to use as a key
|
||||
MessageDigest messageDigest = getMessageDigest();
|
||||
if(messageDigest.getDigestLength() < CIPHER_KEY_BYTES)
|
||||
throw new RuntimeException();
|
||||
// The length of every field must fit in an unsigned 8-bit integer
|
||||
if(rawSecret.length > 255) throw new IllegalArgumentException();
|
||||
if(label.length > 255) throw new IllegalArgumentException();
|
||||
if(initiatorInfo.length > 255) throw new IllegalArgumentException();
|
||||
if(responderInfo.length > 255) throw new IllegalArgumentException();
|
||||
// All fields are length-prefixed
|
||||
messageDigest.update((byte) rawSecret.length);
|
||||
messageDigest.update(rawSecret);
|
||||
messageDigest.update((byte) label.length);
|
||||
messageDigest.update(label);
|
||||
messageDigest.update((byte) initiatorInfo.length);
|
||||
messageDigest.update(initiatorInfo);
|
||||
messageDigest.update((byte) responderInfo.length);
|
||||
messageDigest.update(responderInfo);
|
||||
byte[] hash = messageDigest.digest();
|
||||
// The secret is the first CIPHER_KEY_BYTES bytes of the hash
|
||||
byte[] output = new byte[CIPHER_KEY_BYTES];
|
||||
System.arraycopy(hash, 0, output, 0, output.length);
|
||||
ByteUtils.erase(hash);
|
||||
return output;
|
||||
}
|
||||
|
||||
// Key derivation function based on a PRF in counter mode - see
|
||||
// NIST SP 800-108, section 5.1
|
||||
private byte[] counterModeKdf(byte[] secret, byte[] label, long context) {
|
||||
if(secret.length != CIPHER_KEY_BYTES)
|
||||
throw new IllegalArgumentException();
|
||||
if(Arrays.equals(secret, BLANK_SECRET))
|
||||
throw new IllegalArgumentException();
|
||||
// The label must be null-terminated
|
||||
if(label[label.length - 1] != '\0')
|
||||
throw new IllegalArgumentException();
|
||||
// Initialise the PRF
|
||||
Mac prf = new HMac(new SHA384Digest());
|
||||
KeyParameter k = new KeyParameter(secret);
|
||||
prf.init(k);
|
||||
int macLength = prf.getMacSize();
|
||||
// The output of the PRF must be long enough to use as a key
|
||||
if(macLength < CIPHER_KEY_BYTES) throw new RuntimeException();
|
||||
byte[] mac = new byte[macLength], output = new byte[CIPHER_KEY_BYTES];
|
||||
prf.update((byte) 0); // Counter
|
||||
prf.update(label, 0, label.length); // Null-terminated
|
||||
byte[] contextBytes = new byte[4];
|
||||
ByteUtils.writeUint32(context, contextBytes, 0);
|
||||
prf.update(contextBytes, 0, contextBytes.length);
|
||||
prf.update((byte) CIPHER_KEY_BYTES); // Output length
|
||||
prf.doFinal(mac, 0);
|
||||
System.arraycopy(mac, 0, output, 0, output.length);
|
||||
ByteUtils.erase(mac);
|
||||
ByteUtils.erase(k.getKey());
|
||||
return output;
|
||||
}
|
||||
|
||||
// Password-based key derivation function - see PKCS#5 v2.1, section 5.2
|
||||
private byte[] pbkdf2(char[] password, byte[] salt, int iterations) {
|
||||
byte[] utf8 = toUtf8ByteArray(password);
|
||||
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator();
|
||||
gen.init(utf8, salt, iterations);
|
||||
int keyLengthInBits = CIPHER_KEY_BYTES * 8;
|
||||
CipherParameters p = gen.generateDerivedParameters(keyLengthInBits);
|
||||
ByteUtils.erase(utf8);
|
||||
return ((KeyParameter) p).getKey();
|
||||
}
|
||||
|
||||
// Package access for testing
|
||||
int chooseIterationCount(int targetMillis) {
|
||||
List<Long> quickSamples = new ArrayList<Long>(PBKDF_SAMPLES);
|
||||
List<Long> slowSamples = new ArrayList<Long>(PBKDF_SAMPLES);
|
||||
long iterationNanos = 0, initNanos = 0;
|
||||
while(iterationNanos <= 0 || initNanos <= 0) {
|
||||
// Sample the running time with one iteration and two iterations
|
||||
for(int i = 0; i < PBKDF_SAMPLES; i++) {
|
||||
quickSamples.add(sampleRunningTime(1));
|
||||
slowSamples.add(sampleRunningTime(2));
|
||||
}
|
||||
// Calculate the iteration time and the initialisation time
|
||||
long quickMedian = median(quickSamples);
|
||||
long slowMedian = median(slowSamples);
|
||||
iterationNanos = slowMedian - quickMedian;
|
||||
initNanos = quickMedian - iterationNanos;
|
||||
if(LOG.isLoggable(INFO)) {
|
||||
LOG.info("Init: " + initNanos + ", iteration: "
|
||||
+ iterationNanos);
|
||||
}
|
||||
}
|
||||
long targetNanos = targetMillis * 1000L * 1000L;
|
||||
long iterations = (targetNanos - initNanos) / iterationNanos;
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Target iterations: " + iterations);
|
||||
if(iterations < 1) return 1;
|
||||
if(iterations > Integer.MAX_VALUE) return Integer.MAX_VALUE;
|
||||
return (int) iterations;
|
||||
}
|
||||
|
||||
private long sampleRunningTime(int iterations) {
|
||||
byte[] password = { 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' };
|
||||
byte[] salt = new byte[PBKDF_SALT_BYTES];
|
||||
int keyLengthInBits = CIPHER_KEY_BYTES * 8;
|
||||
long start = System.nanoTime();
|
||||
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator();
|
||||
gen.init(password, salt, iterations);
|
||||
gen.generateDerivedParameters(keyLengthInBits);
|
||||
return System.nanoTime() - start;
|
||||
}
|
||||
|
||||
private long median(List<Long> list) {
|
||||
int size = list.size();
|
||||
if(size == 0) throw new IllegalArgumentException();
|
||||
Collections.sort(list);
|
||||
if(size % 2 == 1) return list.get(size / 2);
|
||||
return list.get(size / 2 - 1) + list.get(size / 2) / 2;
|
||||
}
|
||||
|
||||
byte[] toUtf8ByteArray(char[] c) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
try {
|
||||
Strings.toUTF8ByteArray(c, out);
|
||||
byte[] utf8 = out.toByteArray();
|
||||
// Erase the output stream's buffer
|
||||
out.reset();
|
||||
out.write(new byte[utf8.length]);
|
||||
return utf8;
|
||||
} catch(IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
briar-core/src/org/briarproject/crypto/CryptoModule.java
Normal file
50
briar-core/src/org/briarproject/crypto/CryptoModule.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.api.lifecycle.LifecycleManager;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
|
||||
public class CryptoModule extends AbstractModule {
|
||||
|
||||
/** The maximum number of executor threads. */
|
||||
private static final int MAX_EXECUTOR_THREADS =
|
||||
Runtime.getRuntime().availableProcessors();
|
||||
|
||||
private final ExecutorService cryptoExecutor;
|
||||
|
||||
public CryptoModule() {
|
||||
// The queue is unbounded, so tasks can be dependent
|
||||
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
|
||||
// Discard tasks that are submitted during shutdown
|
||||
RejectedExecutionHandler policy =
|
||||
new ThreadPoolExecutor.DiscardPolicy();
|
||||
// Create a limited # of threads and keep them in the pool for 60 secs
|
||||
cryptoExecutor = new ThreadPoolExecutor(0, MAX_EXECUTOR_THREADS,
|
||||
60, SECONDS, queue, policy);
|
||||
}
|
||||
|
||||
protected void configure() {
|
||||
bind(CryptoComponent.class).to(
|
||||
CryptoComponentImpl.class).in(Singleton.class);
|
||||
}
|
||||
|
||||
@Provides @Singleton @CryptoExecutor
|
||||
Executor getCryptoExecutor(LifecycleManager lifecycleManager) {
|
||||
lifecycleManager.registerForShutdown(cryptoExecutor);
|
||||
return cryptoExecutor;
|
||||
}
|
||||
}
|
||||
63
briar-core/src/org/briarproject/crypto/DoubleDigest.java
Normal file
63
briar-core/src/org/briarproject/crypto/DoubleDigest.java
Normal file
@@ -0,0 +1,63 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import org.briarproject.api.crypto.MessageDigest;
|
||||
|
||||
import org.spongycastle.crypto.Digest;
|
||||
|
||||
/**
|
||||
* A message digest that prevents length extension attacks - see Ferguson and
|
||||
* Schneier, <i>Practical Cryptography</i>, chapter 6.
|
||||
* <p>
|
||||
* "Let h be an interative hash function. The hash function h<sub>d</sub> is
|
||||
* defined by h<sub>d</sub> := h(h(m)), and has a claimed security level of
|
||||
* min(k, n/2) where k is the security level of h and n is the size of the hash
|
||||
* result."
|
||||
*/
|
||||
class DoubleDigest implements MessageDigest {
|
||||
|
||||
private final Digest delegate;
|
||||
|
||||
DoubleDigest(Digest delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public byte[] digest() {
|
||||
byte[] digest = new byte[delegate.getDigestSize()];
|
||||
delegate.doFinal(digest, 0); // h(m)
|
||||
delegate.update(digest, 0, digest.length);
|
||||
delegate.doFinal(digest, 0); // h(h(m))
|
||||
return digest;
|
||||
}
|
||||
|
||||
public byte[] digest(byte[] input) {
|
||||
delegate.update(input, 0, input.length);
|
||||
return digest();
|
||||
}
|
||||
|
||||
public int digest(byte[] buf, int offset, int len) {
|
||||
byte[] digest = digest();
|
||||
len = Math.min(len, digest.length);
|
||||
System.arraycopy(digest, 0, buf, offset, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
public int getDigestLength() {
|
||||
return delegate.getDigestSize();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
delegate.reset();
|
||||
}
|
||||
|
||||
public void update(byte input) {
|
||||
delegate.update(input);
|
||||
}
|
||||
|
||||
public void update(byte[] input) {
|
||||
delegate.update(input, 0, input.length);
|
||||
}
|
||||
|
||||
public void update(byte[] input, int offset, int len) {
|
||||
delegate.update(input, offset, len);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import org.spongycastle.crypto.params.ECDomainParameters;
|
||||
import org.spongycastle.math.ec.ECCurve;
|
||||
import org.spongycastle.math.ec.ECFieldElement;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
|
||||
/** Parameters for curve brainpoolP384r1 - see RFC 5639. */
|
||||
interface EllipticCurveConstants {
|
||||
|
||||
/**
|
||||
* The prime specifying the finite field. (This is called p in RFC 5639 and
|
||||
* q in SEC 2.)
|
||||
*/
|
||||
BigInteger P = new BigInteger("8CB91E82" + "A3386D28" + "0F5D6F7E" +
|
||||
"50E641DF" + "152F7109" + "ED5456B4" + "12B1DA19" + "7FB71123" +
|
||||
"ACD3A729" + "901D1A71" + "87470013" + "3107EC53", 16);
|
||||
|
||||
/**
|
||||
* A coefficient of the equation y^2 = x^3 + A*x + B defining the elliptic
|
||||
* curve. (This is called A in RFC 5639 and a in SEC 2.)
|
||||
*/
|
||||
BigInteger A = new BigInteger("7BC382C6" + "3D8C150C" + "3C72080A" +
|
||||
"CE05AFA0" + "C2BEA28E" + "4FB22787" + "139165EF" + "BA91F90F" +
|
||||
"8AA5814A" + "503AD4EB" + "04A8C7DD" + "22CE2826", 16);
|
||||
|
||||
/**
|
||||
* A coefficient of the equation y^2 = x^3 + A*x + B defining the elliptic
|
||||
* curve. (This is called B in RFC 5639 b in SEC 2.)
|
||||
*/
|
||||
BigInteger B = new BigInteger("04A8C7DD" + "22CE2826" + "8B39B554" +
|
||||
"16F0447C" + "2FB77DE1" + "07DCD2A6" + "2E880EA5" + "3EEB62D5" +
|
||||
"7CB43902" + "95DBC994" + "3AB78696" + "FA504C11", 16);
|
||||
|
||||
/**
|
||||
* The x co-ordinate of the base point G. (This is called x in RFC 5639 and
|
||||
* SEC 2.)
|
||||
*/
|
||||
BigInteger X = new BigInteger("1D1C64F0" + "68CF45FF" + "A2A63A81" +
|
||||
"B7C13F6B" + "8847A3E7" + "7EF14FE3" + "DB7FCAFE" + "0CBD10E8" +
|
||||
"E826E034" + "36D646AA" + "EF87B2E2" + "47D4AF1E", 16);
|
||||
|
||||
/**
|
||||
* The y co-ordinate of the base point G. (This is called y in RFC 5639 and
|
||||
* SEC 2.)
|
||||
*/
|
||||
BigInteger Y = new BigInteger("8ABE1D75" + "20F9C2A4" + "5CB1EB8E" +
|
||||
"95CFD552" + "62B70B29" + "FEEC5864" + "E19C054F" + "F9912928" +
|
||||
"0E464621" + "77918111" + "42820341" + "263C5315", 16);
|
||||
|
||||
/**
|
||||
* The order of the base point G. (This is called q in RFC 5639 and n in
|
||||
* SEC 2.)
|
||||
*/
|
||||
BigInteger Q = new BigInteger("8CB91E82" + "A3386D28" + "0F5D6F7E" +
|
||||
"50E641DF" + "152F7109" + "ED5456B3" + "1F166E6C" + "AC0425A7" +
|
||||
"CF3AB6AF" + "6B7FC310" + "3B883202" + "E9046565", 16);
|
||||
|
||||
/** The cofactor of G. (This is called h in RFC 5639 and SEC 2.) */
|
||||
BigInteger H = BigInteger.ONE;
|
||||
|
||||
// Static parameter objects derived from the above parameters
|
||||
ECCurve CURVE = new ECCurve.Fp(P, A, B);
|
||||
ECPoint G = new ECPoint.Fp(CURVE,
|
||||
new ECFieldElement.Fp(P, X),
|
||||
new ECFieldElement.Fp(P, Y));
|
||||
ECDomainParameters PARAMETERS = new ECDomainParameters(CURVE, G, Q, H);
|
||||
}
|
||||
41
briar-core/src/org/briarproject/crypto/PseudoRandomImpl.java
Normal file
41
briar-core/src/org/briarproject/crypto/PseudoRandomImpl.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import org.briarproject.api.crypto.MessageDigest;
|
||||
import org.briarproject.api.crypto.PseudoRandom;
|
||||
import org.briarproject.util.ByteUtils;
|
||||
|
||||
class PseudoRandomImpl implements PseudoRandom {
|
||||
|
||||
private final MessageDigest messageDigest;
|
||||
|
||||
private byte[] state;
|
||||
private int offset;
|
||||
|
||||
PseudoRandomImpl(MessageDigest messageDigest, int seed1, int seed2) {
|
||||
this.messageDigest = messageDigest;
|
||||
byte[] seedBytes = new byte[8];
|
||||
ByteUtils.writeUint32(seed1, seedBytes, 0);
|
||||
ByteUtils.writeUint32(seed2, seedBytes, 4);
|
||||
messageDigest.update(seedBytes);
|
||||
state = messageDigest.digest();
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
public synchronized byte[] nextBytes(int bytes) {
|
||||
byte[] b = new byte[bytes];
|
||||
int half = state.length / 2;
|
||||
int off = 0, len = b.length, available = half - offset;
|
||||
while(available < len) {
|
||||
System.arraycopy(state, offset, b, off, available);
|
||||
off += available;
|
||||
len -= available;
|
||||
messageDigest.update(state, half, half);
|
||||
state = messageDigest.digest();
|
||||
offset = 0;
|
||||
available = half;
|
||||
}
|
||||
System.arraycopy(state, offset, b, off, len);
|
||||
offset += len;
|
||||
return b;
|
||||
}
|
||||
}
|
||||
86
briar-core/src/org/briarproject/crypto/Sec1KeyParser.java
Normal file
86
briar-core/src/org/briarproject/crypto/Sec1KeyParser.java
Normal file
@@ -0,0 +1,86 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import org.briarproject.api.crypto.KeyParser;
|
||||
import org.briarproject.api.crypto.PrivateKey;
|
||||
import org.briarproject.api.crypto.PublicKey;
|
||||
|
||||
import org.spongycastle.crypto.params.ECDomainParameters;
|
||||
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.spongycastle.math.ec.ECFieldElement;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
|
||||
/**
|
||||
* A key parser that uses the encoding defined in "SEC 1: Elliptic Curve
|
||||
* Cryptography", section 2.3 (Certicom Corporation, May 2009). Point
|
||||
* compression is not used.
|
||||
*/
|
||||
class Sec1KeyParser implements KeyParser {
|
||||
|
||||
private final ECDomainParameters params;
|
||||
private final BigInteger modulus;
|
||||
private final int keyBits, bytesPerInt, publicKeyBytes, privateKeyBytes;
|
||||
|
||||
Sec1KeyParser(ECDomainParameters params, BigInteger modulus, int keyBits) {
|
||||
this.params = params;
|
||||
this.modulus = modulus;
|
||||
this.keyBits = keyBits;
|
||||
bytesPerInt = (keyBits + 7) / 8;
|
||||
publicKeyBytes = 1 + 2 * bytesPerInt;
|
||||
privateKeyBytes = bytesPerInt;
|
||||
}
|
||||
|
||||
public PublicKey parsePublicKey(byte[] encodedKey)
|
||||
throws GeneralSecurityException {
|
||||
// The validation procedure comes from SEC 1, section 3.2.2.1. Note
|
||||
// that SEC 1 parameter names are used below, not RFC 5639 names
|
||||
if(encodedKey.length != publicKeyBytes)
|
||||
throw new GeneralSecurityException();
|
||||
// The first byte must be 0x04
|
||||
if(encodedKey[0] != 4) throw new GeneralSecurityException();
|
||||
// The x co-ordinate must be >= 0 and < p
|
||||
byte[] xBytes = new byte[bytesPerInt];
|
||||
System.arraycopy(encodedKey, 1, xBytes, 0, bytesPerInt);
|
||||
BigInteger x = new BigInteger(1, xBytes); // Positive signum
|
||||
if(x.compareTo(modulus) >= 0) throw new GeneralSecurityException();
|
||||
// The y co-ordinate must be >= 0 and < p
|
||||
byte[] yBytes = new byte[bytesPerInt];
|
||||
System.arraycopy(encodedKey, 1 + bytesPerInt, yBytes, 0, bytesPerInt);
|
||||
BigInteger y = new BigInteger(1, yBytes); // Positive signum
|
||||
if(y.compareTo(modulus) >= 0) throw new GeneralSecurityException();
|
||||
// Verify that y^2 == x^3 + ax + b (mod p)
|
||||
BigInteger a = params.getCurve().getA().toBigInteger();
|
||||
BigInteger b = params.getCurve().getB().toBigInteger();
|
||||
BigInteger lhs = y.multiply(y).mod(modulus);
|
||||
BigInteger rhs = x.multiply(x).add(a).multiply(x).add(b).mod(modulus);
|
||||
if(!lhs.equals(rhs)) throw new GeneralSecurityException();
|
||||
// We know the point (x, y) is on the curve, so we can create the point
|
||||
ECFieldElement elementX = new ECFieldElement.Fp(modulus, x);
|
||||
ECFieldElement elementY = new ECFieldElement.Fp(modulus, y);
|
||||
ECPoint pub = new ECPoint.Fp(params.getCurve(), elementX, elementY);
|
||||
// Verify that the point (x, y) is not the point at infinity
|
||||
if(pub.isInfinity()) throw new GeneralSecurityException();
|
||||
// Verify that the point (x, y) times n is the point at infinity
|
||||
if(!pub.multiply(params.getN()).isInfinity())
|
||||
throw new GeneralSecurityException();
|
||||
// Construct a public key from the point (x, y) and the params
|
||||
ECPublicKeyParameters k = new ECPublicKeyParameters(pub, params);
|
||||
return new Sec1PublicKey(k, keyBits);
|
||||
}
|
||||
|
||||
public PrivateKey parsePrivateKey(byte[] encodedKey)
|
||||
throws GeneralSecurityException {
|
||||
if(encodedKey.length != privateKeyBytes)
|
||||
throw new GeneralSecurityException();
|
||||
BigInteger d = new BigInteger(1, encodedKey); // Positive signum
|
||||
// Verify that the private value is < n
|
||||
if(d.compareTo(params.getN()) >= 0)
|
||||
throw new GeneralSecurityException();
|
||||
// Construct a private key from the private value and the params
|
||||
ECPrivateKeyParameters k = new ECPrivateKeyParameters(d, params);
|
||||
return new Sec1PrivateKey(k, keyBits);
|
||||
}
|
||||
}
|
||||
27
briar-core/src/org/briarproject/crypto/Sec1PrivateKey.java
Normal file
27
briar-core/src/org/briarproject/crypto/Sec1PrivateKey.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import org.briarproject.api.crypto.PrivateKey;
|
||||
|
||||
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
||||
|
||||
class Sec1PrivateKey implements PrivateKey {
|
||||
|
||||
private final ECPrivateKeyParameters key;
|
||||
private final int bytesPerInt;
|
||||
|
||||
Sec1PrivateKey(ECPrivateKeyParameters key, int keyBits) {
|
||||
this.key = key;
|
||||
bytesPerInt = (keyBits + 7) / 8;
|
||||
}
|
||||
|
||||
public byte[] getEncoded() {
|
||||
byte[] encodedKey = new byte[bytesPerInt];
|
||||
byte[] d = key.getD().toByteArray();
|
||||
Sec1Utils.convertToFixedLength(d, encodedKey, bytesPerInt, 0);
|
||||
return encodedKey;
|
||||
}
|
||||
|
||||
ECPrivateKeyParameters getKey() {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
37
briar-core/src/org/briarproject/crypto/Sec1PublicKey.java
Normal file
37
briar-core/src/org/briarproject/crypto/Sec1PublicKey.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import org.briarproject.api.crypto.PublicKey;
|
||||
|
||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||
|
||||
/**
|
||||
* An elliptic curve public key that uses the encoding defined in "SEC 1:
|
||||
* Elliptic Curve Cryptography", section 2.3 (Certicom Corporation, May 2009).
|
||||
* Point compression is not used.
|
||||
*/
|
||||
class Sec1PublicKey implements PublicKey {
|
||||
|
||||
private final ECPublicKeyParameters key;
|
||||
private final int bytesPerInt, publicKeyBytes;
|
||||
|
||||
Sec1PublicKey(ECPublicKeyParameters key, int keyBits) {
|
||||
this.key = key;
|
||||
bytesPerInt = (keyBits + 7) / 8;
|
||||
publicKeyBytes = 1 + 2 * bytesPerInt;
|
||||
}
|
||||
|
||||
public byte[] getEncoded() {
|
||||
byte[] encodedKey = new byte[publicKeyBytes];
|
||||
encodedKey[0] = 4;
|
||||
byte[] x = key.getQ().getX().toBigInteger().toByteArray();
|
||||
Sec1Utils.convertToFixedLength(x, encodedKey, bytesPerInt, 1);
|
||||
byte[] y = key.getQ().getY().toBigInteger().toByteArray();
|
||||
Sec1Utils.convertToFixedLength(y, encodedKey, bytesPerInt,
|
||||
1 + bytesPerInt);
|
||||
return encodedKey;
|
||||
}
|
||||
|
||||
ECPublicKeyParameters getKey() {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
15
briar-core/src/org/briarproject/crypto/Sec1Utils.java
Normal file
15
briar-core/src/org/briarproject/crypto/Sec1Utils.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
class Sec1Utils {
|
||||
|
||||
static void convertToFixedLength(byte[] src, byte[] dest, int destLen,
|
||||
int destOff) {
|
||||
if(src.length < destLen) {
|
||||
destOff += destLen - src.length;
|
||||
System.arraycopy(src, 0, dest, destOff, src.length);
|
||||
} else {
|
||||
int srcOff = src.length - destLen;
|
||||
System.arraycopy(src, srcOff, dest, destOff, destLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
30
briar-core/src/org/briarproject/crypto/SecretKeyImpl.java
Normal file
30
briar-core/src/org/briarproject/crypto/SecretKeyImpl.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
import org.briarproject.util.ByteUtils;
|
||||
|
||||
class SecretKeyImpl implements SecretKey {
|
||||
|
||||
private final byte[] key;
|
||||
|
||||
private boolean erased = false; // Locking: this
|
||||
|
||||
SecretKeyImpl(byte[] key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public synchronized byte[] getEncoded() {
|
||||
if(erased) throw new IllegalStateException();
|
||||
return key;
|
||||
}
|
||||
|
||||
public SecretKey copy() {
|
||||
return new SecretKeyImpl(key.clone());
|
||||
}
|
||||
|
||||
public synchronized void erase() {
|
||||
if(erased) throw new IllegalStateException();
|
||||
ByteUtils.erase(key);
|
||||
erased = true;
|
||||
}
|
||||
}
|
||||
58
briar-core/src/org/briarproject/crypto/SignatureImpl.java
Normal file
58
briar-core/src/org/briarproject/crypto/SignatureImpl.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import org.briarproject.api.crypto.PrivateKey;
|
||||
import org.briarproject.api.crypto.PublicKey;
|
||||
import org.briarproject.api.crypto.Signature;
|
||||
|
||||
import org.spongycastle.crypto.digests.SHA384Digest;
|
||||
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.spongycastle.crypto.params.ParametersWithRandom;
|
||||
import org.spongycastle.crypto.signers.DSADigestSigner;
|
||||
import org.spongycastle.crypto.signers.ECDSASigner;
|
||||
|
||||
class SignatureImpl implements Signature {
|
||||
|
||||
private final SecureRandom secureRandom;
|
||||
private final DSADigestSigner signer;
|
||||
|
||||
SignatureImpl(SecureRandom secureRandom) {
|
||||
this.secureRandom = secureRandom;
|
||||
signer = new DSADigestSigner(new ECDSASigner(), new SHA384Digest());
|
||||
}
|
||||
|
||||
public void initSign(PrivateKey k) throws GeneralSecurityException {
|
||||
if(!(k instanceof Sec1PrivateKey)) throw new GeneralSecurityException();
|
||||
ECPrivateKeyParameters priv = ((Sec1PrivateKey) k).getKey();
|
||||
signer.init(true, new ParametersWithRandom(priv, secureRandom));
|
||||
}
|
||||
|
||||
public void initVerify(PublicKey k) throws GeneralSecurityException {
|
||||
if(!(k instanceof Sec1PublicKey)) throw new GeneralSecurityException();
|
||||
ECPublicKeyParameters pub = ((Sec1PublicKey) k).getKey();
|
||||
signer.init(false, pub);
|
||||
}
|
||||
|
||||
public void update(byte b) {
|
||||
signer.update(b);
|
||||
}
|
||||
|
||||
public void update(byte[] b) {
|
||||
update(b, 0, b.length);
|
||||
}
|
||||
|
||||
public void update(byte[] b, int off, int len) {
|
||||
signer.update(b, off, len);
|
||||
}
|
||||
|
||||
public byte[] sign() {
|
||||
return signer.generateSignature();
|
||||
}
|
||||
|
||||
public boolean verify(byte[] signature) {
|
||||
return signer.verifySignature(signature);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user