-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6c5b0ca
commit 4989077
Showing
8 changed files
with
325 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/* | ||
* Copyright © 2017 Coda Hale ([email protected]) | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.xconn.cryptobox; | ||
|
||
import java.nio.charset.StandardCharsets; | ||
|
||
import org.bouncycastle.crypto.engines.Salsa20Engine; | ||
import org.bouncycastle.util.Pack; | ||
|
||
/** | ||
* An implementation of the HSalsa20 hash based on the Bouncy Castle Salsa20 core. | ||
*/ | ||
class HSalsa20 { | ||
|
||
private static final byte[] SIGMA = "expand 32-byte k".getBytes(StandardCharsets.US_ASCII); | ||
private static final int SIGMA_0 = Pack.littleEndianToInt(SIGMA, 0); | ||
private static final int SIGMA_4 = Pack.littleEndianToInt(SIGMA, 4); | ||
private static final int SIGMA_8 = Pack.littleEndianToInt(SIGMA, 8); | ||
private static final int SIGMA_12 = Pack.littleEndianToInt(SIGMA, 12); | ||
|
||
static void hsalsa20(byte[] out, byte[] in, byte[] k) { | ||
final int[] x = new int[16]; | ||
|
||
final int in0 = Pack.littleEndianToInt(in, 0); | ||
final int in4 = Pack.littleEndianToInt(in, 4); | ||
final int in8 = Pack.littleEndianToInt(in, 8); | ||
final int in12 = Pack.littleEndianToInt(in, 12); | ||
|
||
x[0] = SIGMA_0; | ||
x[1] = Pack.littleEndianToInt(k, 0); | ||
x[2] = Pack.littleEndianToInt(k, 4); | ||
x[3] = Pack.littleEndianToInt(k, 8); | ||
x[4] = Pack.littleEndianToInt(k, 12); | ||
x[5] = SIGMA_4; | ||
x[6] = in0; | ||
x[7] = in4; | ||
x[8] = in8; | ||
x[9] = in12; | ||
x[10] = SIGMA_8; | ||
x[11] = Pack.littleEndianToInt(k, 16); | ||
x[12] = Pack.littleEndianToInt(k, 20); | ||
x[13] = Pack.littleEndianToInt(k, 24); | ||
x[14] = Pack.littleEndianToInt(k, 28); | ||
x[15] = SIGMA_12; | ||
|
||
Salsa20Engine.salsaCore(20, x, x); | ||
|
||
x[0] -= SIGMA_0; | ||
x[5] -= SIGMA_4; | ||
x[10] -= SIGMA_8; | ||
x[15] -= SIGMA_12; | ||
x[6] -= in0; | ||
x[7] -= in4; | ||
x[8] -= in8; | ||
x[9] -= in12; | ||
|
||
Pack.intToLittleEndian(x[0], out, 0); | ||
Pack.intToLittleEndian(x[5], out, 4); | ||
Pack.intToLittleEndian(x[10], out, 8); | ||
Pack.intToLittleEndian(x[15], out, 12); | ||
Pack.intToLittleEndian(x[6], out, 16); | ||
Pack.intToLittleEndian(x[7], out, 20); | ||
Pack.intToLittleEndian(x[8], out, 24); | ||
Pack.intToLittleEndian(x[9], out, 28); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package io.xconn.cryptobox; | ||
|
||
public class KeyPair<PublicKey, PrivateKey> { | ||
private final PublicKey publicKey; | ||
private final PrivateKey privateKey; | ||
|
||
public KeyPair(PublicKey publicKey, PrivateKey privateKey) { | ||
this.publicKey = publicKey; | ||
this.privateKey = privateKey; | ||
} | ||
|
||
public PublicKey getPublicKey() { | ||
return publicKey; | ||
} | ||
|
||
public PrivateKey getPrivateKey() { | ||
return privateKey; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package io.xconn.cryptobox; | ||
|
||
import org.bouncycastle.crypto.digests.Blake2bDigest; | ||
import org.bouncycastle.crypto.engines.XSalsa20Engine; | ||
import org.bouncycastle.crypto.macs.Poly1305; | ||
import org.bouncycastle.crypto.params.KeyParameter; | ||
import org.bouncycastle.crypto.params.ParametersWithIV; | ||
import org.bouncycastle.math.ec.rfc7748.X25519; | ||
import org.bouncycastle.util.Arrays; | ||
|
||
import static io.xconn.cryptobox.SecretBox.boxOpen; | ||
import static io.xconn.cryptobox.Util.MAC_SIZE; | ||
import static io.xconn.cryptobox.Util.PUBLIC_KEY_BYTES; | ||
import static io.xconn.cryptobox.Util.getX25519PublicKey; | ||
|
||
public class SealedBox { | ||
private static final byte[] HSALSA20_SEED = new byte[16]; | ||
|
||
public static byte[] seal(byte[] message, byte[] recipientPublicKey) { | ||
byte[] cipherText = new byte[message.length + PUBLIC_KEY_BYTES + MAC_SIZE]; | ||
seal(cipherText, message, recipientPublicKey); | ||
return cipherText; | ||
} | ||
|
||
public static void seal(byte[] output, byte[] message, byte[] recipientPublicKey) { | ||
KeyPair<byte[], byte[]> keyPair = Util.generateX25519KeyPair(); | ||
byte[] nonce = createNonce(keyPair.getPublicKey(), recipientPublicKey); | ||
byte[] sharedSecret = computeSharedSecret(recipientPublicKey, keyPair.getPrivateKey()); | ||
|
||
XSalsa20Engine cipher = new XSalsa20Engine(); | ||
ParametersWithIV params = new ParametersWithIV(new KeyParameter(sharedSecret), nonce); | ||
cipher.init(true, params); | ||
|
||
byte[] sk = new byte[Util.SECRET_KEY_LEN]; | ||
cipher.processBytes(sk, 0, sk.length, sk, 0); | ||
|
||
// encrypt the message | ||
byte[] ciphertext = new byte[message.length]; | ||
cipher.processBytes(message, 0, message.length, ciphertext, 0); | ||
|
||
// create the MAC | ||
Poly1305 mac = new Poly1305(); | ||
byte[] macBuf = new byte[mac.getMacSize()]; | ||
mac.init(new KeyParameter(sk)); | ||
mac.update(ciphertext, 0, ciphertext.length); | ||
mac.doFinal(macBuf, 0); | ||
|
||
System.arraycopy(keyPair.getPublicKey(), 0, output, 0, keyPair.getPublicKey().length); | ||
System.arraycopy(macBuf, 0, output, keyPair.getPublicKey().length, macBuf.length); | ||
System.arraycopy(ciphertext, 0, output, keyPair.getPublicKey().length + macBuf.length, ciphertext.length); | ||
} | ||
|
||
static byte[] createNonce(byte[] ephemeralPublicKey, byte[] recipientPublicKey) { | ||
Blake2bDigest blake2b = new Blake2bDigest(Util.NONCE_SIZE * 8); | ||
byte[] nonce = new byte[blake2b.getDigestSize()]; | ||
|
||
blake2b.update(ephemeralPublicKey, 0, ephemeralPublicKey.length); | ||
blake2b.update(recipientPublicKey, 0, recipientPublicKey.length); | ||
|
||
blake2b.doFinal(nonce, 0); | ||
|
||
return nonce; | ||
} | ||
|
||
static byte[] computeSharedSecret(byte[] publicKey, byte[] privateKey) { | ||
byte[] sharedSecret = new byte[32]; | ||
// compute the raw shared secret | ||
X25519.scalarMult(privateKey, 0, publicKey, 0, sharedSecret, 0); | ||
// encrypt the shared secret | ||
byte[] key = new byte[32]; | ||
HSalsa20.hsalsa20(key, HSALSA20_SEED, sharedSecret); | ||
return key; | ||
} | ||
|
||
public static byte[] sealOpen(byte[] message, byte[] privateKey) { | ||
byte[] plainText = new byte[message.length - PUBLIC_KEY_BYTES - MAC_SIZE]; | ||
sealOpen(plainText, message, privateKey); | ||
return plainText; | ||
} | ||
|
||
public static void sealOpen(byte[] output, byte[] message, byte[] privateKey) { | ||
byte[] ephemeralPublicKey = Arrays.copyOf(message, PUBLIC_KEY_BYTES); | ||
byte[] ciphertext = Arrays.copyOfRange(message, PUBLIC_KEY_BYTES, message.length); | ||
byte[] nonce = createNonce(ephemeralPublicKey, getX25519PublicKey(privateKey)); | ||
byte[] sharedSecret = computeSharedSecret(ephemeralPublicKey, privateKey); | ||
|
||
boxOpen(output, nonce, ciphertext, sharedSecret); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package io.xconn.cryptobox; | ||
|
||
import org.bouncycastle.util.encoders.Hex; | ||
import org.junit.jupiter.api.BeforeAll; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertArrayEquals; | ||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
import static org.junit.jupiter.api.Assertions.assertThrows; | ||
|
||
import static io.xconn.cryptobox.SealedBox.computeSharedSecret; | ||
import static io.xconn.cryptobox.SealedBox.createNonce; | ||
import static io.xconn.cryptobox.SealedBox.sealOpen; | ||
import static io.xconn.cryptobox.SealedBox.seal; | ||
import static io.xconn.cryptobox.Util.MAC_SIZE; | ||
import static io.xconn.cryptobox.Util.PUBLIC_KEY_BYTES; | ||
|
||
public class SealedBoxTest { | ||
|
||
private static byte[] publicKey; | ||
private static byte[] privateKey; | ||
|
||
@BeforeAll | ||
public static void setUp() { | ||
publicKey = Hex.decode("e146721761cf7378cb2e007adc1a51b70fa40abfb87652c645d8e86be19c2b1e"); | ||
privateKey = Hex.decode("3817e2630237d569188a02a06354d9e9f61ee9cdd0cc8b5388c56013b7b5654a"); | ||
} | ||
|
||
@Test | ||
public void testEncryptAndDecrypt() { | ||
String message = "Hello, world!"; | ||
byte[] encrypted = SealedBox.seal(message.getBytes(), publicKey); | ||
byte[] decrypted = SealedBox.sealOpen(encrypted, privateKey); | ||
|
||
assertArrayEquals(message.getBytes(), decrypted); | ||
} | ||
|
||
@Test | ||
public void testEncryptAndDecryptOutput() { | ||
String message = "Hello, world!"; | ||
|
||
byte[] encrypted = new byte[message.getBytes().length + PUBLIC_KEY_BYTES + MAC_SIZE]; | ||
seal(encrypted, message.getBytes(), publicKey); | ||
byte[] decrypted = new byte[message.length()]; | ||
sealOpen(decrypted, encrypted, privateKey); | ||
|
||
assertArrayEquals(message.getBytes(), decrypted); | ||
} | ||
|
||
@Test | ||
void testCreateNonce() { | ||
byte[] nonce = createNonce(new byte[32], new byte[32]); | ||
assertNotNull(nonce); | ||
assertEquals(Util.NONCE_SIZE, nonce.length); | ||
} | ||
|
||
@Test | ||
public void testComputeSharedSecret() { | ||
byte[] sharedSecret = computeSharedSecret(publicKey, privateKey); | ||
|
||
byte[] expectedSharedSecret = Hex.decode("544b3aea8fcebe9e986a1628e517927526407c100d09e17c5dc7dd81149325e1"); | ||
assertArrayEquals(expectedSharedSecret, sharedSecret); | ||
} | ||
|
||
|
||
@Test | ||
public void testInvalidDecrypt() { | ||
byte[] encrypted = SealedBox.seal("Hello, world!".getBytes(), publicKey); | ||
|
||
// Using a different private key for decryption | ||
byte[] wrongPrivateKey = Hex.decode("0000000000000000000000000000000000000000000000000000000000000000"); | ||
|
||
assertThrows(IllegalArgumentException.class, () -> SealedBox.sealOpen(encrypted, wrongPrivateKey)); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters