Skip to content

Commit

Permalink
Use X.509 encoding of public keys
Browse files Browse the repository at this point in the history
This removes the potential side channel problems with compressing the public keys and defers it to the JDK or security provider implementation. This also allows for supporting the other elliptic curves with little modification.
  • Loading branch information
jvz committed Apr 23, 2021
1 parent ab584a8 commit 349df82
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 351 deletions.
160 changes: 79 additions & 81 deletions src/main/java/dev/o1c/jcryptobox/Box.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,140 +2,138 @@

import javax.crypto.AEADBadTagException;
import javax.crypto.KeyAgreement;
import java.security.InvalidAlgorithmParameterException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;

public class Box {

public static KeyPair generateKeyPair() {
KeyPairGenerator keyPairGenerator = getKeyPairGenerator();
KeyPairGenerator keyPairGenerator = getECGenerator();
keyPairGenerator.initialize(256);
return keyPairGenerator.generateKeyPair();
}

public static byte[] box(KeyPair sender, PublicKey recipient, byte[] nonce, byte[] message) {
validateArgs(sender, recipient);
ECPublicKey senderKey = (ECPublicKey) sender.getPublic();
ECPublicKey recipientKey = (ECPublicKey) recipient;
MessageDigest sha256 = getSha256();

sha256.update(generateSharedSecret(recipient, sender.getPrivate()));
sha256.update(NistCurve.compress(senderKey));
sha256.update(NistCurve.compress(recipientKey));
byte[] sharedKey = sha256.digest();

return SecretBox.box(sharedKey, nonce, message);
public static byte[] box(KeyPair sender, PublicKey recipient, byte[] nonce, byte[] message) throws InvalidKeyException {
byte[] key = generateBoxKey(sender, recipient);
return SecretBox.box(key, nonce, message);
}

public static byte[] open(KeyPair recipient, PublicKey sender, byte[] nonce, byte[] box) throws AEADBadTagException {
validateArgs(recipient, sender);
ECPublicKey recipientKey = (ECPublicKey) recipient.getPublic();
ECPublicKey senderKey = (ECPublicKey) sender;
MessageDigest sha256 = getSha256();

sha256.update(generateSharedSecret(senderKey, recipient.getPrivate()));
sha256.update(NistCurve.compress(senderKey));
sha256.update(NistCurve.compress(recipientKey));
byte[] sharedKey = sha256.digest();

return SecretBox.open(sharedKey, nonce, box);
public static byte[] open(KeyPair recipient, PublicKey sender, byte[] nonce, byte[] box) throws AEADBadTagException, InvalidKeyException {
byte[] key = generateBoxKey(sender, recipient);
return SecretBox.open(key, nonce, box);
}

public static byte[] seal(PublicKey recipient, byte[] message) {
if (!(recipient instanceof ECPublicKey)) {
throw new IllegalArgumentException("Recipient key must be an EC key");
}
ECPublicKey recipientKey = (ECPublicKey) recipient;
byte[] recipientBytes = NistCurve.compress(recipientKey.getW(), recipientKey.getParams().getCurve());
public static byte[] seal(PublicKey recipient, byte[] message) throws InvalidKeyException {
KeyPair sender = generateKeyPair();
ECPublicKey senderKey = (ECPublicKey) sender.getPublic();
byte[] senderBytes = NistCurve.compress(senderKey);
byte[] key = generateBoxKey(sender, recipient);

MessageDigest sha256 = getSha256();
sha256.update(senderBytes);
sha256.update(recipientBytes);
byte[] ephemeralKey = sender.getPublic().getEncoded();
sha256.update(ephemeralKey);
sha256.update(recipient.getEncoded());
byte[] nonce = sha256.digest();

sha256.reset();
sha256.update(generateSharedSecret(recipient, sender.getPrivate()));
sha256.update(senderBytes);
sha256.update(recipientBytes);
byte[] sharedKey = sha256.digest();

byte[] box = Arrays.copyOf(senderBytes, senderBytes.length + message.length + SecretBox.TAG_BYTES);
SecretBox.box(sharedKey, nonce, message, 0, message.length, box, senderBytes.length);
int ekLength = ephemeralKey.length;
byte[] box = new byte[1 + ekLength + message.length + SecretBox.TAG_BYTES];
box[0] = (byte) ekLength;
System.arraycopy(ephemeralKey, 0, box, 1, ekLength);
SecretBox.box(key, nonce, message, 0, message.length, box, ekLength + 1);
return box;
}

public static byte[] unseal(KeyPair recipient, byte[] sealedBox) throws AEADBadTagException {
if (!(recipient.getPrivate() instanceof ECPrivateKey)) {
throw new IllegalArgumentException("Recipient key must be an EC key");
public static byte[] unseal(KeyPair recipient, byte[] sealedBox) throws AEADBadTagException, InvalidKeyException {
int ekLength = Byte.toUnsignedInt(sealedBox[0]);
int messageLength = sealedBox.length - 1 - ekLength - SecretBox.TAG_BYTES;
if (messageLength < 0) {
throw new IllegalArgumentException("Sealed box too small");
}
KeySpec ephemeralKey = new X509EncodedKeySpec(Arrays.copyOfRange(sealedBox, 1, 1 + ekLength));
PublicKey publicKey;
try {
publicKey = getECFactory().generatePublic(ephemeralKey);
} catch (InvalidKeySpecException e) {
throw new IllegalArgumentException(e);
}
ECPublicKey recipientKey = (ECPublicKey) recipient.getPublic();
byte[] recipientBytes = NistCurve.compress(recipientKey.getW(), recipientKey.getParams().getCurve());
byte[] senderBytes = Arrays.copyOf(sealedBox, recipientBytes.length);
// TODO: determine NistCurve from recipient params instead of hard coding
ECPublicKey senderKey = NistCurve.P256.decodeKey(senderBytes);
byte[] key = generateBoxKey(publicKey, recipient);

MessageDigest sha256 = getSha256();
sha256.update(senderBytes);
sha256.update(recipientBytes);
sha256.update(publicKey.getEncoded());
sha256.update(recipient.getPublic().getEncoded());
byte[] nonce = sha256.digest();

sha256.reset();
sha256.update(generateSharedSecret(senderKey, recipient.getPrivate()));
sha256.update(senderBytes);
sha256.update(recipientBytes);
byte[] sharedKey = sha256.digest();
return SecretBox.open(key, nonce, sealedBox, 1 + ekLength, messageLength);
}

private static byte[] generateBoxKey(KeyPair sender, PublicKey recipient) throws InvalidKeyException {
KeyAgreement kx = getECDH();
kx.init(sender.getPrivate());
kx.doPhase(recipient, true);
Mac kdf = getHmac();
kdf.init(new SecretKeySpec(kx.generateSecret(), "KDF"));
kdf.update(sender.getPublic().getEncoded());
kdf.update(recipient.getEncoded());
return kdf.doFinal();
}

byte[] message = new byte[sealedBox.length - senderBytes.length - SecretBox.TAG_BYTES];
SecretBox.open(sharedKey, nonce, sealedBox, senderBytes.length, message.length, message, 0);
return message;
private static byte[] generateBoxKey(PublicKey sender, KeyPair recipient) throws InvalidKeyException {
KeyAgreement kx = getECDH();
kx.init(recipient.getPrivate());
kx.doPhase(sender, true);
Mac kdf = getHmac();
kdf.init(new SecretKeySpec(kx.generateSecret(), "KDF"));
kdf.update(sender.getEncoded());
kdf.update(recipient.getPublic().getEncoded());
return kdf.doFinal();
}

private static void validateArgs(KeyPair keyPair, PublicKey publicKey) {
if (!(keyPair.getPrivate() instanceof ECPrivateKey && publicKey instanceof ECPublicKey)) {
throw new IllegalArgumentException("Sender and recipient must be EC keys");
private static KeyAgreement getECDH() {
try {
return KeyAgreement.getInstance("ECDH");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}

private static KeyFactory getECFactory() {
try {
NistCurve.validatePublicKey((ECPublicKey) publicKey, (ECPrivateKey) keyPair.getPrivate());
} catch (InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
return KeyFactory.getInstance("EC");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}

private static KeyPairGenerator getKeyPairGenerator() {
private static KeyPairGenerator getECGenerator() {
try {
return KeyPairGenerator.getInstance("EC");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}

private static MessageDigest getSha256() {
private static Mac getHmac() {
try {
return MessageDigest.getInstance("SHA-256");
return Mac.getInstance("HmacSHA256");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}

private static byte[] generateSharedSecret(PublicKey publicKey, PrivateKey privateKey) {
private static MessageDigest getSha256() {
try {
KeyAgreement ecdh = KeyAgreement.getInstance("ECDH");
ecdh.init(privateKey);
ecdh.doPhase(publicKey, true);
return ecdh.generateSecret();
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new IllegalArgumentException(e);
return MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}

Expand Down
Loading

0 comments on commit 349df82

Please sign in to comment.