mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-15 20:29:52 +01:00
ECIES encryption for feedback and crash reports.
This commit is contained in:
27
briar-core/src/org/briarproject/crypto/AsciiArmour.java
Normal file
27
briar-core/src/org/briarproject/crypto/AsciiArmour.java
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package org.briarproject.crypto;
|
||||||
|
|
||||||
|
import org.briarproject.api.FormatException;
|
||||||
|
import org.briarproject.util.StringUtils;
|
||||||
|
|
||||||
|
class AsciiArmour {
|
||||||
|
|
||||||
|
static String wrap(byte[] b, int lineLength) {
|
||||||
|
String wrapped = StringUtils.toHexString(b);
|
||||||
|
StringBuilder s = new StringBuilder();
|
||||||
|
int length = wrapped.length();
|
||||||
|
for (int i = 0; i < length; i += lineLength) {
|
||||||
|
int end = Math.min(i + lineLength, length);
|
||||||
|
s.append(wrapped.substring(i, end));
|
||||||
|
s.append("\r\n");
|
||||||
|
}
|
||||||
|
return s.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte[] unwrap(String s) throws FormatException {
|
||||||
|
try {
|
||||||
|
return StringUtils.fromHexString(s.replaceAll("[^0-9a-fA-F]", ""));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new FormatException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
207
briar-core/src/org/briarproject/crypto/MessageEncrypter.java
Normal file
207
briar-core/src/org/briarproject/crypto/MessageEncrypter.java
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
package org.briarproject.crypto;
|
||||||
|
|
||||||
|
import org.briarproject.util.StringUtils;
|
||||||
|
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
||||||
|
import org.spongycastle.asn1.x9.X9ECParameters;
|
||||||
|
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
||||||
|
import org.spongycastle.crypto.BasicAgreement;
|
||||||
|
import org.spongycastle.crypto.BlockCipher;
|
||||||
|
import org.spongycastle.crypto.CipherParameters;
|
||||||
|
import org.spongycastle.crypto.CryptoException;
|
||||||
|
import org.spongycastle.crypto.DerivationFunction;
|
||||||
|
import org.spongycastle.crypto.KeyEncoder;
|
||||||
|
import org.spongycastle.crypto.KeyParser;
|
||||||
|
import org.spongycastle.crypto.Mac;
|
||||||
|
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||||
|
import org.spongycastle.crypto.digests.SHA256Digest;
|
||||||
|
import org.spongycastle.crypto.engines.AESLightEngine;
|
||||||
|
import org.spongycastle.crypto.engines.IESEngine;
|
||||||
|
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
|
||||||
|
import org.spongycastle.crypto.generators.EphemeralKeyPairGenerator;
|
||||||
|
import org.spongycastle.crypto.generators.KDF2BytesGenerator;
|
||||||
|
import org.spongycastle.crypto.macs.HMac;
|
||||||
|
import org.spongycastle.crypto.modes.CBCBlockCipher;
|
||||||
|
import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||||
|
import org.spongycastle.crypto.params.AsymmetricKeyParameter;
|
||||||
|
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.crypto.params.IESWithCipherParameters;
|
||||||
|
import org.spongycastle.crypto.parsers.ECIESPublicKeyParser;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
public class MessageEncrypter {
|
||||||
|
|
||||||
|
private static final ECDomainParameters PARAMETERS;
|
||||||
|
private static final int MAC_KEY_BITS = 256;
|
||||||
|
private static final int CIPHER_KEY_BITS = 256;
|
||||||
|
private static final int LINE_LENGTH = 70;
|
||||||
|
|
||||||
|
static {
|
||||||
|
X9ECParameters x9 = TeleTrusTNamedCurves.getByName("brainpoolp512r1");
|
||||||
|
PARAMETERS = new ECDomainParameters(x9.getCurve(), x9.getG(),
|
||||||
|
x9.getN(), x9.getH());
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ECKeyPairGenerator generator;
|
||||||
|
private final EphemeralKeyPairGenerator ephemeralGenerator;
|
||||||
|
private final KeyParser parser;
|
||||||
|
|
||||||
|
MessageEncrypter(SecureRandom random) {
|
||||||
|
generator = new ECKeyPairGenerator();
|
||||||
|
generator.init(new ECKeyGenerationParameters(PARAMETERS, random));
|
||||||
|
KeyEncoder encoder = new PublicKeyEncoder();
|
||||||
|
ephemeralGenerator = new EphemeralKeyPairGenerator(generator, encoder);
|
||||||
|
parser = new PublicKeyParser(PARAMETERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsymmetricCipherKeyPair generateKeyPair() {
|
||||||
|
return generator.generateKeyPair();
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] encrypt(ECPublicKeyParameters pubKey, byte[] plaintext)
|
||||||
|
throws CryptoException {
|
||||||
|
IESEngine engine = getEngine();
|
||||||
|
engine.init(pubKey, getCipherParameters(), ephemeralGenerator);
|
||||||
|
return engine.processBlock(plaintext, 0, plaintext.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] decrypt(ECPrivateKeyParameters privKey, byte[] ciphertext)
|
||||||
|
throws CryptoException {
|
||||||
|
IESEngine engine = getEngine();
|
||||||
|
engine.init(privKey, getCipherParameters(), parser);
|
||||||
|
return engine.processBlock(ciphertext, 0, ciphertext.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IESEngine getEngine() {
|
||||||
|
BasicAgreement agreement = new ECDHCBasicAgreement();
|
||||||
|
DerivationFunction kdf = new KDF2BytesGenerator(new SHA256Digest());
|
||||||
|
Mac mac = new HMac(new SHA256Digest());
|
||||||
|
BlockCipher cipher = new CBCBlockCipher(new AESLightEngine());
|
||||||
|
PaddedBufferedBlockCipher pad = new PaddedBufferedBlockCipher(cipher);
|
||||||
|
return new IESEngine(agreement, kdf, mac, pad);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CipherParameters getCipherParameters() {
|
||||||
|
return new IESWithCipherParameters(null, null, MAC_KEY_BITS,
|
||||||
|
CIPHER_KEY_BITS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PublicKeyEncoder implements KeyEncoder {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getEncoded(AsymmetricKeyParameter key) {
|
||||||
|
if (!(key instanceof ECPublicKeyParameters))
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
return ((ECPublicKeyParameters) key).getQ().getEncoded(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PublicKeyParser extends ECIESPublicKeyParser {
|
||||||
|
|
||||||
|
private PublicKeyParser(ECDomainParameters ecParams) {
|
||||||
|
super(ecParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AsymmetricKeyParameter readKey(InputStream in)
|
||||||
|
throws IOException {
|
||||||
|
try {
|
||||||
|
return super.readKey(in);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
if (args.length < 1) {
|
||||||
|
printUsage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SecureRandom random = new SecureRandom();
|
||||||
|
MessageEncrypter encrypter = new MessageEncrypter(random);
|
||||||
|
if (args[0].equals("generate")) {
|
||||||
|
if (args.length != 3) {
|
||||||
|
printUsage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Generate a key pair
|
||||||
|
AsymmetricCipherKeyPair keyPair = encrypter.generateKeyPair();
|
||||||
|
ECPublicKeyParameters publicKey =
|
||||||
|
(ECPublicKeyParameters) keyPair.getPublic();
|
||||||
|
byte[] publicKeyBytes = publicKey.getQ().getEncoded(false);
|
||||||
|
PrintStream out = new PrintStream(new FileOutputStream(args[1]));
|
||||||
|
out.print(StringUtils.toHexString(publicKeyBytes));
|
||||||
|
out.flush();
|
||||||
|
out.close();
|
||||||
|
ECPrivateKeyParameters privateKey =
|
||||||
|
(ECPrivateKeyParameters) keyPair.getPrivate();
|
||||||
|
out = new PrintStream(new FileOutputStream(args[2]));
|
||||||
|
out.print(privateKey.getD().toString(16).toUpperCase());
|
||||||
|
out.flush();
|
||||||
|
out.close();
|
||||||
|
} else if (args[0].equals("encrypt")) {
|
||||||
|
if (args.length != 2) {
|
||||||
|
printUsage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Encrypt a decrypted message
|
||||||
|
InputStream in = new FileInputStream(args[1]);
|
||||||
|
byte[] b = StringUtils.fromHexString(readFully(in).trim());
|
||||||
|
in = new ByteArrayInputStream(b);
|
||||||
|
ECPublicKeyParameters publicKey =
|
||||||
|
(ECPublicKeyParameters) encrypter.parser.readKey(in);
|
||||||
|
String message = readFully(System.in);
|
||||||
|
byte[] plaintext = message.getBytes(Charset.forName("UTF-8"));
|
||||||
|
byte[] ciphertext = encrypter.encrypt(publicKey, plaintext);
|
||||||
|
System.out.println(AsciiArmour.wrap(ciphertext, LINE_LENGTH));
|
||||||
|
} else if (args[0].equals("decrypt")) {
|
||||||
|
if (args.length != 2) {
|
||||||
|
printUsage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Decrypt an encrypted message
|
||||||
|
InputStream in = new FileInputStream(args[1]);
|
||||||
|
byte[] b = StringUtils.fromHexString(readFully(in).trim());
|
||||||
|
BigInteger d = new BigInteger(1, b);
|
||||||
|
ECPrivateKeyParameters privateKey = new ECPrivateKeyParameters(d,
|
||||||
|
PARAMETERS);
|
||||||
|
byte[] ciphertext = AsciiArmour.unwrap(readFully(System.in));
|
||||||
|
byte[] plaintext = encrypter.decrypt(privateKey, ciphertext);
|
||||||
|
System.out.println(new String(plaintext, Charset.forName("UTF-8")));
|
||||||
|
} else {
|
||||||
|
printUsage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void printUsage() {
|
||||||
|
System.err.println("Usage:");
|
||||||
|
System.err.println("MessageEncrypter generate <public_key_file> <private_key_file>");
|
||||||
|
System.err.println("MessageEncrypter encrypt <public_key_file>");
|
||||||
|
System.err.println("MessageEncrypter decrypt <private_key_file>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readFully(InputStream in) throws IOException {
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
Scanner scanner = new Scanner(in);
|
||||||
|
while (scanner.hasNextLine()) {
|
||||||
|
stringBuilder.append(scanner.nextLine());
|
||||||
|
stringBuilder.append(System.lineSeparator());
|
||||||
|
}
|
||||||
|
scanner.close();
|
||||||
|
in.close();
|
||||||
|
return stringBuilder.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
47
briar-tests/src/org/briarproject/crypto/AsciiArmourTest.java
Normal file
47
briar-tests/src/org/briarproject/crypto/AsciiArmourTest.java
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package org.briarproject.crypto;
|
||||||
|
|
||||||
|
import org.briarproject.BriarTestCase;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class AsciiArmourTest extends BriarTestCase {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWrapOnSingleLine() {
|
||||||
|
byte[] b = new byte[8];
|
||||||
|
for (int i = 0; i < b.length; i++) b[i] = (byte) i;
|
||||||
|
String expected = "0001020304050607\r\n";
|
||||||
|
assertEquals(expected, AsciiArmour.wrap(b, 70));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWrapOnMultipleLines() {
|
||||||
|
byte[] b = new byte[8];
|
||||||
|
for (int i = 0; i < b.length; i++) b[i] = (byte) i;
|
||||||
|
String expected = "0001020\r\n3040506\r\n07\r\n";
|
||||||
|
assertEquals(expected, AsciiArmour.wrap(b, 7));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnwrapOnSingleLine() throws Exception {
|
||||||
|
String s = "0001020304050607";
|
||||||
|
byte[] expected = new byte[] {0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
|
assertArrayEquals(expected, AsciiArmour.unwrap(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnwrapOnMultipleLines() throws Exception {
|
||||||
|
String s = "0001020\r\n3040506\r\n07";
|
||||||
|
byte[] expected = new byte[] {0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
|
assertArrayEquals(expected, AsciiArmour.unwrap(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnwrapWithJunkCharacters() throws Exception {
|
||||||
|
String s = "0001??020\rzz\n30z40..506\r\n07;;";
|
||||||
|
byte[] expected = new byte[] {0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
|
assertArrayEquals(expected, AsciiArmour.unwrap(s));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package org.briarproject.crypto;
|
||||||
|
|
||||||
|
import org.briarproject.BriarTestCase;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
||||||
|
import org.spongycastle.crypto.CryptoException;
|
||||||
|
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
||||||
|
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
|
||||||
|
public class MessageEncrypterTest extends BriarTestCase {
|
||||||
|
|
||||||
|
private final SecureRandom random = new SecureRandom();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncryptionAndDecryption() throws Exception {
|
||||||
|
MessageEncrypter m = new MessageEncrypter(random);
|
||||||
|
AsymmetricCipherKeyPair kp = m.generateKeyPair();
|
||||||
|
ECPublicKeyParameters pub = (ECPublicKeyParameters) kp.getPublic();
|
||||||
|
ECPrivateKeyParameters priv = (ECPrivateKeyParameters) kp.getPrivate();
|
||||||
|
byte[] plaintext = new byte[123];
|
||||||
|
random.nextBytes(plaintext);
|
||||||
|
byte[] ciphertext = m.encrypt(pub, plaintext);
|
||||||
|
byte[] decrypted = m.decrypt(priv, ciphertext);
|
||||||
|
assertArrayEquals(plaintext, decrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = CryptoException.class)
|
||||||
|
public void testDecryptionFailsWithAlteredCiphertext() throws Exception {
|
||||||
|
MessageEncrypter m = new MessageEncrypter(random);
|
||||||
|
AsymmetricCipherKeyPair kp = m.generateKeyPair();
|
||||||
|
ECPublicKeyParameters pub = (ECPublicKeyParameters) kp.getPublic();
|
||||||
|
ECPrivateKeyParameters priv = (ECPrivateKeyParameters) kp.getPrivate();
|
||||||
|
byte[] ciphertext = m.encrypt(pub, new byte[123]);
|
||||||
|
ciphertext[random.nextInt(ciphertext.length)] ^= 0xFF;
|
||||||
|
m.decrypt(priv, ciphertext);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user