This library implements a set of "fluent" API builders for the java.security
classes, and provides more typesafe, intuitive API to access trust stores, key stores and keys. The primary purpose of this library is to make small tasks easy, and provide better integration with the JSSE stack.
Also check out pgpainless which does roughly the same thing with PGP API.
In your pom.xml:
<dependency>
<groupId>com.tersesystems.securitybuilder</groupId>
<artifactId>securitybuilder</artifactId>
<version>1.0.1</version><!-- see badge for latest version -->
</dependency>
libraryDependencies += "com.tersesystems.securitybuilder" % "securitybuilder" % "1.0.1"
The primary use of this package is to set up test X.509 certificates, private keys and trust stores. The Java Cryptography Architecture lays out how to create and initialize certificates, keystores, and so on, but typically does so in frustrating ways.
The assumption is that you'll be working with Java 1.8 but with decent algorithms, so there are a number of preset defaults. The builders are thread-safe and only build when you pull the trigger, but assume immutable input, so don't pass in arrays or lists that you are still fiddling with.
All the classes are in com.tersesystems.securitybuilder
package.
import com.tersesystems.securitybuilder.*;
In general, if you're just using the JCA, there are some based off Latacora's Cryptographic Right Answers:
- Use RSA with 2048 bit key length and SHA-2 for public and private keys.
- Use AES-GCM for encryption but SEE WARNING BELOW, and never reuse the IV. There is no provable difference between AES-128 and AES-256, so don't worry about it and use AES-256.
- If you're going over the network, you generally want full on TLS, so use JSSE with debugjsse as the provider to see what's going on under the hood. Be aware that SSLEngine/SSLSocket does not know that you're using HTTPS, so you need to define hostname verification yourself by setting
sslParameters.setEndpointIdentificationAlgorithm("HTTPS")
. - Use an HMAC with at least SHA256, and a secret key that has at least 96 bits of entropy --
EntropySource.salt()
uses 256 bits. - Use a MessageDigest with at least SHA256.
- Use PBKDF2 with a SHA-2 HMAC if you have to, but if you can use jBCrypt or scrypt go with that.
- There's no real need to use your own SecureRandom, and you don't need to use
useInstanceStrong
, the entropy pool is the same and you may get blocking. UseEntropySource
.
Please be aware that some of the algorithms in the JCA are way, way out of date.
If you need a cryptography API, DON'T USE THE JCA! Even with these builders, building your own crypto using a low level library is like juggling chainsaws in the dark. In particular, low level libraries don't do key management and key rotation very well.
Use Google Tink instead, which has support for storing keysets, symmetric key encryption, digital signatures, envelope encryption and key rotation.
Google Tink doesn't do everything: in that case, I recommend looking for a fallback professional high-level library rather than rolling your own. This will typically involve an binding on top of a C library like lazysodium on top of libsodium, or a specialized crypto framework like Noise-Java implementing the Noise Protocol Framework.
Creates an X509Certificate or a chain of X509Certificate.
Very useful for building up certificates if you use chain()
.
public class X509CertificateCreatorTest {
@Test
public void testFunctionalStyle() throws Exception {
FinalStage<RSAKeyPair> keyPairCreator = KeyPairCreator.creator().withRSA().withKeySize(2048);
RSAKeyPair rootKeyPair = keyPairCreator.create();
RSAKeyPair intermediateKeyPair = keyPairCreator.create();
RSAKeyPair eePair = keyPairCreator.create();
IssuerStage<RSAPrivateKey> creator =
X509CertificateCreator.creator().withSHA256withRSA().withDuration(Duration.ofDays(365));
String issuer = "CN=letsencrypt.derp,O=Root CA";
X509Certificate[] chain =
creator
.withRootCA(issuer, rootKeyPair, 2)
.chain(
rootKeyPair.getPrivate(),
rootCreator ->
rootCreator
.withPublicKey(intermediateKeyPair.getPublic())
.withSubject("OU=intermediate CA")
.withCertificateAuthorityExtensions(0)
.chain(
intermediateKeyPair.getPrivate(),
intCreator ->
intCreator
.withPublicKey(eePair.getPublic())
.withSubject("CN=tersesystems.com")
.withEndEntityExtensions()
.chain()))
.create();
PrivateKeyStore privateKeyStore =
PrivateKeyStore.create("tersesystems.com", eePair.getPrivate(), chain);
TrustStore trustStore = TrustStore.create(singletonList(chain[2]), cert -> "letsencrypt.derp");
try {
final PKIXCertPathValidatorResult result = CertificateChainValidator.validator()
.withAnchor(new TrustAnchor(issuer, rootKeyPair.getPublic(), null))
.withCertificates(chain)
.validate();
final PublicKey subjectPublicKey = result.getPublicKey();
assertThat(subjectPublicKey).isEqualTo(eePair.getPublic());
} catch (final CertPathValidatorException cpve) {
fail("Cannot test exception", cpve);
}
SSLContext sslContext =
SSLContextBuilder.builder()
.withTLS()
.withKeyManager(
KeyManagerBuilder.builder()
.withSunX509()
.withPrivateKeyStore(privateKeyStore)
.build())
.withTrustManager(
TrustManagerBuilder.builder()
.withDefaultAlgorithm()
.withTrustStore(trustStore)
.build())
.build();
assertThat(sslContext).isNotNull();
}
}
Admittedly this doesn't look very simple, but you should see the code it replaces.
Builds a java.security.Certificate
from a source.
If you use withX509()
, it will give you an X509Certificate
.
public class CertificateBuilderTest {
@Test
public void testX509Certificate() {
final InputStream inputStream = getClass().getResourceAsStream("/playframework.pem");
try {
final X509Certificate x509Certificate =
CertificateBuilder.builder()
.withX509()
.withInputStream(inputStream)
.build();
assertThat(x509Certificate.getSigAlgName()).isEqualTo("SHA256withECDSA");
} catch (final CertificateException e) {
fail(e.getMessage(), e);
}
}
}
Builds a KeyManager
from input. If you use withNewSunX509()
, then you get a X509ExtendedKeyManager
that can differentiate between RSA / DSA keys, pick out unexpired keys, and use password specific entries out of the store (if you use the keystore builder defined below). See Key Managers and Key Stores for the gory details.
Recommend using with debugjsse provider.
public class KeyManagerBuilderTest {
@Test
public void testKeyManagerWithKeyStore() {
try {
final KeyStore keyStore = KeyStoreBuilder.empty();
final X509ExtendedKeyManager keyManager =
KeyManagerBuilder.builder()
.withNewSunX509()
.withKeyStore(keyStore, "".toCharArray())
.build();
assertThat(keyManager.getPrivateKey("derp")).isNull();
} catch (final GeneralSecurityException e) {
fail(e.getMessage(), e);
}
}
}
Builds a TrustManager
from input.
Recommend using with debugjsse provider.
public class TrustManagerBuilderTest {
@Test
void builderWithKeyStore() throws Exception {
final KeyStore keyStore = KeyStoreBuilder.empty();
final X509ExtendedTrustManager trustManager =
TrustManagerBuilder.builder().withDefaultAlgorithm().withKeyStore(keyStore).build();
assertThat(trustManager.getAcceptedIssuers()).isEmpty();
}
}
Build a SSLContext
.
You will typically want to combine this with TrustManagerBuilder
and KeyManagerBuilder
.
Recommend using with debugjsse provider.
public class SSLContextBuilderTest {
@Test
public void testSSLContextBuilderWithTLS() {
try {
final SSLContext sslContext = SSLContextBuilder.builder().withTLS().build();
sslContext.createSSLEngine();
} catch (final GeneralSecurityException e) {
fail(e.getMessage(), e);
}
}
@Test
public void testSSLContextBuilderWithTLSAndKeyManager() {
try {
final X509ExtendedKeyManager km =
KeyManagerBuilder.builder().withNewSunX509().withDefaultKeyStoreAndPassword().build();
final SSLContext sslContext =
SSLContextBuilder.builder().withTLS().withKeyManager(km).build();
sslContext.createSSLEngine();
} catch (final GeneralSecurityException e) {
fail(e.getMessage(), e);
}
}
}
Validates a certificate chain using a PKIX CertPathValidator
. See Java PKI Programmer's Guide for details, but you can safely ignore most if not all of it.
public class CertificateChainValidatorTest {
public void testCertificate(Certificate[] chain, X509Certificate rootCertificate) {
try {
final PKIXCertPathValidatorResult result = CertificateChainValidator.validator()
.withTrustedCertificates(rootCertificate)
.withCertificates(chain)
.validate();
final PublicKey subjectPublicKey = result.getPublicKey();
assertThat(subjectPublicKey).isEqualTo(eePair.getPublic());
} catch (final CertPathValidatorException cpve) {
fail("Cannot test exception", cpve);
}
}
}
Key stores are used for key management. The java.security.KeyStore
has three wrappers, depending on purpose: PrivateKeyStore
, TrustStore
, and SecretKeyStore
. They all extend AbstractKeyStore
, and are written to be a drop in for java.util.Map
. See blog post for gory details.
Sets up a private keystore that is set up the way that the default SunX509 keymanager expects -- that is, all the private keys have the same password. You work with PrivateKeyEntry
and never have to provide the password as a parameter.
public class PrivateKeyStoreTest {
@Test
public void testAdd() {
try {
final char[] password = "".toCharArray();
final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
final PrivateKeyStore privateKeyStore = PrivateKeyStore.create(keyStore, password);
final RSAKeyPair rsaKeyPair = KeyPairCreator.creator().withRSA().withKeySize(2048).build();
final X509Certificate rsaCertificate =
X509CertificateCreator.builder()
.withSHA256withRSA()
.withNotBeforeNow()
.withDuration(Duration.ofDays(365))
.withRootCA("CN=example.com", rsaKeyPair, 2)
.build();
final PrivateKeyEntry entry =
new PrivateKeyEntry(rsaKeyPair.getPrivate(), new Certificate[] {rsaCertificate});
privateKeyStore.put("alias1", entry);
// PrivateKey doesn't override equals!
assertThat(Arrays.equals(privateKeyStore.get("alias1").getPrivateKey().getEncoded(), (entry.getPrivateKey().getEncoded()))).isTrue();
} catch (final Exception e) {
fail(e.getMessage());
}
}
}
TrustStore
is a wrapper around KeyStore
for TrustedCertificateEntry
.
public class TrustStoreTest {
@Test
void testSize() {
try {
final KeyStore keyStore = generateStore();
final TrustStore trustStore = TrustStore.create(keyStore);
final RSAKeyPair rsaKeyPair = KeyPairCreator.creator().withRSA().withKeySize(2048).build();
final DSAKeyPair dsaKeyPair = KeyPairCreator.creator().withDSA().withKeySize(1024).build();
final X509Certificate rsaCertificate =
X509CertificateCreator.builder()
.withSHA256withRSA()
.withDuration(Duration.ofDays(365))
.withRootCA("CN=example.com", rsaKeyPair, 2)
.create();
final X509Certificate dsaCertificate =
X509CertificateCreator.builder()
.withSignatureAlgorithm("SHA256withDSA")
.withDuration(Duration.ofDays(365))
.withRootCA("CN=example.com", dsaKeyPair.getKeyPair(), 2)
.create();
trustStore.put("rsaentry", new TrustedCertificateEntry(rsaCertificate));
trustStore.put("dsaentry", new TrustedCertificateEntry(dsaCertificate));
assertThat(trustStore.size()).isEqualTo(2);
} catch (final Exception e) {
fail(e.getMessage());
}
}
}
A KeyStore
that contains only SecretKeyEntry
.
Use this with a KeyStore format of type PKCS12.
public class SecretKeyStoreTest {
@Test
void testSize() {
try {
String password = "hello world".toCharArray();
byte[] salt = EntropySource.salt();
final Map<String, ProtectionParameter> passwordMap =
Collections.singletonMap("username", new PasswordProtection(password));
final SecretKeyStore secretKeyStore = generateSecretKeyStore(passwordMap);
PBEKey secretKey = PasswordBuilder.builder()
.withPBKDF2WithHmacSHA512()
.withPassword(password)
.withIterations(10000)
.withSalt(salt)
.withKeyLength(64 * 8)
.build();
secretKeyStore.put("username", new SecretKeyEntry(secretKey));
assertThat(secretKeyStore.size()).isEqualTo(1);
} catch (final KeyStoreException
| IOException
| NoSuchAlgorithmException
| CertificateException
| InvalidKeySpecException e) {
fail(e);
}
}
}
Builds a KeyStore
.
public class KeyStoreBuilderTest {
@Test
public void testKeyStoreBuilderWithPathAndNoPassword() {
try {
final Path tempPath = Files.createTempFile(null, null);
final KeyStore keyStore = KeyStoreBuilder.empty();
try (OutputStream outputStream = Files.newOutputStream(tempPath)) {
keyStore.store(outputStream, "".toCharArray());
}
final KeyStore keyStoreFromPath =
KeyStoreBuilder.builder().withDefaultType().withPath(tempPath).withNoPassword().build();
assertThat(keyStoreFromPath.getType()).isEqualTo(KeyStore.getDefaultType());
} catch (final Exception e) {
fail(e.getMessage(), e);
}
}
}
Allows access to the default KeyStore
used for CA certificates and for the private key store location.
Builds a KeyStore.Builder
, using a keystore builder that is able to send different passwords to the "NewSunX509" keymanager.
The out of the box KeyStore.Builder
API does not do this! See blog post for details.
public class DifferentPasswordsTest {
@Test
public void testWithBuilder() throws GeneralSecurityException, IOException {
final char[] password1 = "password1".toCharArray();
final char[] password2 = "password2".toCharArray();
final Map<String, ProtectionParameter> passwordsMap = new HashMap<>();
passwordsMap.put("rsaentry", new PasswordProtection(password1));
passwordsMap.put("dsaentry", new PasswordProtection(password2));
final KeyStore keyStore = generateStore();
final KeyStore.Builder builder =
KeyManagerKeyStoreBuilder.newInstance(keyStore, passwordsMap::get);
final KeyManagerFactory kmf = KeyManagerFactory.getInstance("NewSunX509");
kmf.init(new KeyStoreBuilderParameters(builder));
final X509ExtendedKeyManager keyManager = (X509ExtendedKeyManager) kmf.getKeyManagers()[0];
final String rsaAlias = keyManager.chooseServerAlias("RSA", null, null);
assertThat(rsaAlias).contains("rsaentry");
final PrivateKey rsaPrivateKey = keyManager.getPrivateKey(rsaAlias);
assertThat(rsaPrivateKey).isNotNull(); // can get password
final String dsaAlias = keyManager.chooseServerAlias("DSA", null, null);
assertThat(dsaAlias).contains("dsaentry");
final PrivateKey dsaPrivateKey = keyManager.getPrivateKey(dsaAlias);
assertThat(dsaPrivateKey).isNotNull(); // can get password
}
}
Creates a new KeyPair
containing PublicKey
and PrivateKey
using a KeyPairGenerator
.
If you use withRSA
, withDSA
or withEC
then you get back RSAKeyPair
etc.
class KeyPairCreatorTest {
@Test
void testWithAlgorithm() throws GeneralSecurityException {
final KeyPair keyPair = KeyPairCreator.creator().withAlgorithm("RSA").withKeySize(2048).create();
Assertions.assertThat(keyPair.getPublic().getAlgorithm()).isEqualTo("RSA");
}
@Test
void testWithRSA() throws GeneralSecurityException {
final RSAKeyPair keyPair = KeyPairCreator.creator().withRSA().withKeySize(2048).create();
Assertions.assertThat(keyPair.getPublic().getAlgorithm()).isEqualTo("RSA");
}
@Test
void testWithDSA() throws GeneralSecurityException {
final DSAKeyPair keyPair = KeyPairCreator.creator().withDSA().withKeySize(1024).create();
Assertions.assertThat(keyPair.getPublic().getAlgorithm()).isEqualTo("DSA");
}
@Test
void testWithDH() throws GeneralSecurityException {
final DHKeyPair keyPair = KeyPairCreator.creator().withDH().withKeySize(1024).create();
Assertions.assertThat(keyPair.getPublic().getAlgorithm()).isEqualTo("DH");
}
@Test
void testWithEC() throws GeneralSecurityException {
final ECKeyPair keyPair = KeyPairCreator.creator().withEC().withKeySize(224).create();
Assertions.assertThat(keyPair.getPublic().getAlgorithm()).isEqualTo("EC");
}
}
Builds a EncodedKeySpec
from existing source material.
You can use either PKCS8EncodedKeySpec
, commonly used for PEM encoded private keys, or X509EncodedKeySpec
, used for PEM encoded X.509 certificates.
class PKCS8EncodedKeySpecBuilderTest {
@Test
public void testGeneration() throws Exception {
// Read a private key
final Reader reader = new InputStreamReader(getClass().getResourceAsStream("/private-key.pem"));
final PKCS8EncodedKeySpec keySpec =
PKCS8EncodedKeySpecBuilder.builder().withReader(reader).withNoPassword().build();
assertThat(keySpec.getFormat()).isEqualTo("PKCS#8");
}
}
Builds a PublicKey
.
Will provide public key of the appropriate type using withRSA
, withDSA
, or withEC
methods.
public class PublicKeyBuilderTest {
@Test
public void testRSAPublicKey() throws GeneralSecurityException {
final BigInteger modulus =
new BigInteger(
"b4a7e46170574f16a97082b22be58b6a2a629798419"
+ "be12872a4bdba626cfae9900f76abfb12139dce5de5"
+ "6564fab2b6543165a040c606887420e33d91ed7ed7",
16);
final BigInteger exp = new BigInteger("11", 16);
final RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(modulus, exp);
RSAPublicKey rsaPublicKey =
PublicKeyBuilder.builder().withRSA().withKeySpec(rsaPublicKeySpec).build();
assertThat(rsaPublicKey).isNotNull();
}
}
Builds a PrivateKey
.
Will provide private key of the appropriate type using withRSA
, withDSA
, or withEC
methods.
class PrivateKeyBuilderTest {
@Test
void builderWithRSA() throws GeneralSecurityException {
final RSAPrivateKey exampleKey =
(RSAPrivateKey)
KeyPairCreator.creator().withAlgorithm("RSA").withKeySize(2048).build().getPrivate();
final RSAPrivateKeySpec rsaPrivateKeySpec =
new RSAPrivateKeySpec(exampleKey.getModulus(), exampleKey.getPrivateExponent());
final RSAPrivateKey privateKey =
PrivateKeyBuilder.builder().withRSA().withKeySpec(rsaPrivateKeySpec).build();
assertThat(privateKey).isNotNull();
}
}
Builds a SecretKey
.
The algorithms are in The SunJCE Provider.
public class SecretKeyBuilderTest {
@Test
public void testSecretKeySpec() throws Exception {
byte[] aesKeyData = "abc123".getBytes();
SecretKey secretKey = SecretKeyBuilder.builder()
.withSecretKeySpec("AES")
.withData(aesKeyData)
.build();
assertThat(secretKey.getAlgorithm()).isEqualTo("AES");
}
}
Builds an Message Authentication Code based on cryptographic hashing, aka Mac
.
public class MacBuilderTest {
@Test
void testMacBuild() throws GeneralSecurityException {
SecretKey key = new SecretKeySpec("privatekey".getBytes(), "HmacSHA256");
Mac sha256Mac = MacBuilder.builder().withAlgorithm("HmacSHA256").withKey(key).build();
String output = byteArrayToHex(sha256Mac.doFinal("test".getBytes()));
assertThat(sha256Mac.getAlgorithm()).isEqualTo("HmacSHA256");
assertThat(output).isEqualTo("27f0d5331806fb9f21247b19bee883a7cfe54c069d6e28edccc2cff8e78c4a74");
}
@Test
void testSecretKeySpec() throws GeneralSecurityException {
Mac sha256Mac = MacBuilder.builder().withSecretKeySpec("HmacSHA256").withString("privatekey").build();
String output = byteArrayToHex(sha256Mac.doFinal("test".getBytes()));
assertThat(sha256Mac.getAlgorithm()).isEqualTo("HmacSHA256");
assertThat(output).isEqualTo("27f0d5331806fb9f21247b19bee883a7cfe54c069d6e28edccc2cff8e78c4a74");
}
@Test
void testHmac() throws GeneralSecurityException {
Mac sha256Mac = MacBuilder.builder().withHmacSHA256().withString("privatekey").build();
String output = byteArrayToHex(sha256Mac.doFinal("test".getBytes()));
assertThat(sha256Mac.getAlgorithm()).isEqualTo("HmacSHA256");
assertThat(output).isEqualTo("27f0d5331806fb9f21247b19bee883a7cfe54c069d6e28edccc2cff8e78c4a74");
}
}
Builds a MessageDigest
.
These are intentionally curtailed so you don't pick out weak MessageDigest algorithms.
public class MessageDigestBuilderTest {
@Test
public void testSha512() throws NoSuchAlgorithmException {
assertThat(MessageDigestBuilder.sha512().getAlgorithm()).isEqualTo("SHA-512");
}
}
Builds a Signature
. for either signing or verifying.
public class SignatureBuilderTest {
@Test
public void testSignature() {
try {
final KeyPair<?, ?> keyPair =
KeyPairCreator.creator().withAlgorithm("RSA").withKeySize(2048).build();
final PrivateKey privateKey = keyPair.getPrivate();
final PublicKey publicKey = keyPair.getPublic();
final Signature signingSignature =
SignatureBuilder.builder().withAlgorithm("SHA256withRSA").signing(privateKey).build();
final byte[] digest = signingSignature.sign();
final Signature verifySignature =
SignatureBuilder.builder().withAlgorithm("SHA256withRSA").verifying(publicKey).build();
assertThat(verifySignature.verify(digest)).isEqualTo(true);
} catch (final Exception e) {
Fail.fail(e.getMessage(), e);
}
}
}
Pulls from SecureRandom /dev/urandom
, using the recommended number of random bits. See blog post for details.
public class EntropySource {
/**
* Provides an initialization vector for GCM.
*/
public static byte[] gcmIV() {
return nextBytes(DEFAULT_GCM_IV_LENGTH);
}
/**
* Provides a salt, which must be unique but is not private.
*/
public static byte[] salt() {
return nextBytes(DEFAULT_SALT_LENGTH);
}
}
Finally, there's some code which is useful in a pinch but which doesn't really go anywhere else.
Makes generating an AES-GCM cipher a bit easier. You always want to use an authenticated encryption mode.
Again, you're better off using Google Tink's symmetric encryption if you're doing encryption -- and if you're going over the network, you generally want full on TLS. See the usage and warnings up top.
public class AuthenticatedEncryptionBuilderTest {
@Test
public void testCipher() throws GeneralSecurityException {
final SecretKey aesSecretKey = SecretKeyGenerator.generate().withAES().withKeySize(128).build();
final SecretKeySpec secretKeySpec = new SecretKeySpec(aesSecretKey.getEncoded(), aesSecretKey.getAlgorithm());
final IvStage builder = AuthenticatedEncryptionBuilder.builder().withSecretKey(secretKeySpec);
byte[] gcmIV = EntropySource.gcmIV();
byte[] inputData = "input text".getBytes(UTF_8);
byte[] encryptedData = builder.withIv(gcmIV).encrypt().doFinal(inputData);
byte[] decryptedData = builder.withIv(gcmIV).decrypt().doFinal(encryptedData);
String decryptString = new String(decryptedData, UTF_8);
assertThat(decryptString).isEqualTo("input text");
}
}
A specialized secret key builder for encrypting passwords.
Use PBKDF2 with a SHA-2 HMAC if you have to, but if you can use jBCrypt or scrypt, go with that.
public class PasswordBuilderTest {
@Test
public void testPasswordSpec() throws Exception {
byte[] salt = EntropySource.salt();
PBEKey passwordBasedEncryptionKey = PasswordBuilder.builder()
.withPBKDF2WithHmacSHA512()
.withPassword("hello world".toCharArray())
.withIterations(1000)
.withSalt(salt)
.withKeyLength(64 * 8)
.build();
byte[] encryptedPassword = passwordBasedEncryptionKey.getEncoded();
assertThat(passwordBasedEncryptionKey.getAlgorithm()).isEqualTo("PBKDF2WithHmacSHA512");
}
}
Creates a KeyAgreement instance.
This is typically used with Diffie-Hellman, most commonly found in SSH. Use jsch if you want SSH, otherwise you're better off using a high level crypto library like those described in the WARNING section up top.
The canonical DH key exchange:
public class KeyAgreementBuilderTest {
@Test
public void testKeyAgreementParams() throws GeneralSecurityException, IOException {
// Alice creates her own DH key pair with 2048-bit key size
DHKeyPair aliceKpair = KeyPairCreator.creator().withDH().withKeySize(2048).create();
// Alice creates and initializes her DH KeyAgreement object
KeyAgreement aliceKeyAgree = KeyAgreementBuilder.builder()
.withDH()
.withKey(aliceKpair.getPrivate())
.build();
// Alice encodes her public key, and sends it over to Bob.
byte[] alicePubKeyEnc = aliceKpair.getPublic().getEncoded();
//* Let's turn over to Bob. Bob has received Alice's public key
//* in encoded format.
//* He instantiates a DH public key from the encoded key material.
DHPublicKey alicePubKey = PublicKeyBuilder.builder().withDH()
.withKeySpec(new X509EncodedKeySpec(alicePubKeyEnc)).build();
//* Bob gets the DH parameters associated with Alice's public key.
//* He must use the same parameters when he generates his own key
//* pair.
DHParameterSpec dhParamFromAlicePubKey = alicePubKey.getParams();
// Bob creates his own DH key pair
DHKeyPair bobKpair = KeyPairCreator.creator().withDH().withKeySpec(dhParamFromAlicePubKey)
.create();
// Bob creates and initializes his DH KeyAgreement object
KeyAgreement bobKeyAgree = KeyAgreementBuilder.builder().withDH().withKey(bobKpair.getPrivate())
.build();
// Bob encodes his public key, and sends it over to Alice.
byte[] bobPubKeyEnc = bobKpair.getPublic().getEncoded();
//* Alice uses Bob's public key for the first (and only) phase
//* of her version of the DH protocol.
//* Before she can do so, she has to instantiate a DH public key
//* from Bob's encoded key material.
DHPublicKey bobPubKey = PublicKeyBuilder.builder().withDH()
.withKeySpec(new X509EncodedKeySpec(bobPubKeyEnc)).build();
aliceKeyAgree.doPhase(bobPubKey, true);
//* Bob uses Alice's public key for the first (and only) phase
//* of his version of the DH protocol.
bobKeyAgree.doPhase(alicePubKey, true);
// At this stage, both Alice and Bob have completed the DH key
// agreement protocol. Both generate the (same) shared secret.
byte[] aliceSharedSecret = aliceKeyAgree.generateSecret();
byte[] bobSharedSecret = new byte[aliceSharedSecret.length];
bobKeyAgree.generateSecret(bobSharedSecret, 0);
assertThat(Arrays.equals(aliceSharedSecret, bobSharedSecret)).isTrue();
// Now let's create a SecretKey object using the shared secret
// and use it for encryption.
SecretKeySpec bobAesKey = new SecretKeySpec(bobSharedSecret, 0, 16, "AES");
SecretKeySpec aliceAesKey = new SecretKeySpec(aliceSharedSecret, 0, 16, "AES");
// Bob encrypts, using AES in GCM mode
final byte[] iv = EntropySource.gcmIV();
Cipher bobCipher = AuthenticatedEncryptionBuilder.builder().withSecretKey(bobAesKey).withIv(iv)
.encrypt();
byte[] cleartext = "This is just an example".getBytes();
byte[] ciphertext = bobCipher.doFinal(cleartext);
// Alice decrypts, using AES in GCM mode
Cipher aliceCipher = AuthenticatedEncryptionBuilder.builder().withSecretKey(aliceAesKey).withIv(iv).decrypt();
byte[] recovered = aliceCipher.doFinal(ciphertext);
assertThat(Arrays.equals(cleartext, recovered)).isTrue();
}
}