While trying to implement the "Authenticated encryption" scheme of libsodium using BouncyCastle I naively performed simple X25519 key agreement to obtain a javax.crypto.SecretKey
object.
I.e.:
public SecretKey generateSecretKey(PrivateKey privateKey, PublicKey publicKey) throws NoSuchAlgorithmException, InvalidKeyException {
KeyAgreement keyAgreement = KeyAgreement.getInstance("X25519", new BouncyCastleProvider());
keyAgreement.init(privateKey);
keyAgreement.doPhase(publicKey, true);
return keyAgreement.generateSecret("X25519");
}
I used this secretKey
as an input to the org.bouncycastle.crypto.engines.XSalsa20Engine
:
XSalsa20Engine xSalsa20Engine = new XSalsa20Engine();
xSalsa20Engine.init(true, new ParametersWithIV(new KeyParameter(secretKey), nonce));
However, reading the Cryptography in NaCl paper, and subsequent testing, I found that this is wrong.
Quote:
In the next step, described in Section 7, Alice will convert this 32-byte shared secret k into a 32-byte string HSalsa20(k, 0), which is then used to encrypt and authenticate packets. Bob similarly uses HSalsa20(k, 0) to verify and decrypt the packets. No other use is made of k. One can thus view HSalsa20(k, 0) as the shared secret rather than k.
The secret key produced by the KeyAgreement
needs to be first run through a round of HSalsa20(k, 0)
. to produce the final key, called a "shared secret key" in the original paper by Bernstein, that is then used for encryption.
How can I perform a round of HSalsa20 on the initial SecretKey
object I got from X25519 exchange using BouncyCastle? There is no standalone HSalsa20
function implementation in Bouncycastle library. It seems to be implemented somehow implicitly as a part of the (X)Salsa20Engine class, and I'm not able to invoke it out of the message encryption context.
You can use BouncyCastle for an implementation of HSalsa20 based on an answer to one of your last questions:
BouncyCastle implements
XSalsa20Engine
as derivation ofSalsa20Engine
.XSalsa20Engine
overwritessetKey()
, which implements HSalsa20 and internally determinessubkey
andsubnonce
. When theXSalsa20Engine
instance is initialized,Salsa20Engine#init()
is executed and thussetKey()
.
This offers the following path for an implementation of HSalsa20: Implement a class HSalsa20
that derives from Salsa20Engine
and take the logic from XSalsa20Engine#setKey()
:
import java.io.ByteArrayOutputStream;
import org.bouncycastle.crypto.engines.Salsa20Engine;
import org.bouncycastle.util.Pack;
class HSalsa20 extends Salsa20Engine
{
public byte[] getData(byte[] keyBytes, byte[] ivBytes)
{
super.setKey(keyBytes, ivBytes);
Pack.littleEndianToInt(ivBytes, 8, engineState, 8, 2);
int[] hsalsa20Out = new int[engineState.length];
salsaCore(20, engineState, hsalsa20Out);
engineState[1] = hsalsa20Out[0] - engineState[0];
engineState[2] = hsalsa20Out[5] - engineState[5];
engineState[3] = hsalsa20Out[10] - engineState[10];
engineState[4] = hsalsa20Out[15] - engineState[15];
engineState[11] = hsalsa20Out[6] - engineState[6];
engineState[12] = hsalsa20Out[7] - engineState[7];
engineState[13] = hsalsa20Out[8] - engineState[8];
engineState[14] = hsalsa20Out[9] - engineState[9];
byte[] result = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream( );
outputStream.write( Pack.intToLittleEndian(engineState[1] ));
outputStream.write( Pack.intToLittleEndian(engineState[2] ));
outputStream.write( Pack.intToLittleEndian(engineState[3] ));
outputStream.write( Pack.intToLittleEndian(engineState[4] ));
outputStream.write( Pack.intToLittleEndian(engineState[11] ));
outputStream.write( Pack.intToLittleEndian(engineState[12] ));
outputStream.write( Pack.intToLittleEndian(engineState[13] ));
outputStream.write( Pack.intToLittleEndian(engineState[14] ));
result = outputStream.toByteArray();
} catch(Exception ex) {
ex.printStackTrace(); // your exception handling
}
return result;
}
}
Test: The document you linked has test vectors for HSalsa20 in chapter 8:
import java.util.HexFormat;
...
// test vector 1
HSalsa20 hSalsa20 = new HSalsa20();
byte[] test1 = hSalsa20.getData(
new byte[] {0x4a,0x5d,(byte)0x9d,0x5b,(byte)0xa4,(byte)0xce,0x2d,(byte)0xe1,0x72,(byte)0x8e,0x3b,(byte)0xf4,(byte)0x80,0x35,0x0f,0x25,(byte)0xe0,0x7e,0x21,(byte)0xc9,0x47,(byte)0xd1,(byte)0x9e,0x33,0x76,(byte)0xf0,(byte)0x9b,0x3c,0x1e,0x16,0x17,0x42},
new byte[16]);
System.out.println(HexFormat.of().formatHex(test1)); // 0x1b27556473e985d462cd51197a9a46c76009549eac6474f206c4ee0844f68389
// test vector 2
hSalsa20 = new HSalsa20();
byte[] test2 = hSalsa20.getData(
new byte[] {0x1b,0x27,0x55,0x64,0x73,(byte)0xe9,(byte)0x85,(byte)0xd4,0x62,(byte)0xcd,0x51,0x19,0x7a,(byte)0x9a,0x46,(byte)0xc7,0x60,0x09,0x54,(byte)0x9e,(byte)0xac,0x64,0x74,(byte)0xf2,0x06,(byte)0xc4,(byte)0xee,0x08,0x44,(byte)0xf6,(byte)0x83,(byte)0x89},
new byte[] {0x69,0x69,0x6e,(byte)0xe9,0x55,(byte)0xb6,0x2b,0x73,(byte)0xcd,0x62,(byte)0xbd,(byte)0xa8,0x75,(byte)0xfc,0x73,(byte)0xd6});
System.out.println(HexFormat.of().formatHex(test2)); // 0xdc908dda0b9344a953629b733820778880f3ceb421bb61b91cbd4c3e66256ce4
The outputs generated correspond to those of the test vectors.
This allows Libsodium's crypto_box (public key cryptography) to be implemented with BouncyCastle as follows: