Skip to content

Instantly share code, notes, and snippets.

@martijndwars
Created March 6, 2016 21:51
Show Gist options
  • Save martijndwars/36b6a1f5d9441f6e1e65 to your computer and use it in GitHub Desktop.
Save martijndwars/36b6a1f5d9441f6e1e65 to your computer and use it in GitHub Desktop.
Web push in Java
import java.security.PublicKey;
public class Encrypted {
private final PublicKey publicKey;
private final byte[] salt;
private final byte[] ciphertext;
public Encrypted(final PublicKey publicKey, final byte[] salt, final byte[] ciphertext) {
this.publicKey = publicKey;
this.salt = salt;
this.ciphertext = ciphertext;
}
public PublicKey getPublicKey() {
return publicKey;
}
public byte[] getSalt() {
return salt;
}
public byte[] getCiphertext() {
return ciphertext;
}
public static class Builder {
private PublicKey publicKey;
private byte[] salt;
private byte[] ciphertext;
public Builder withPublicKey(PublicKey publicKey) {
this.publicKey = publicKey;
return this;
}
public Builder withSalt(byte[] salt) {
this.salt = salt;
return this;
}
public Builder withCiphertext(byte[] ciphertext) {
this.ciphertext = ciphertext;
return this;
}
public Encrypted build() {
return new Encrypted(publicKey, salt, ciphertext);
}
}
}
import com.google.common.primitives.Bytes;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.util.Arrays;
import java.util.Random;
public class Main {
public static void main(String[] args) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException {
// Usage:
// encrypt(publicKey, payload);
}
/**
* Encrypt the payload using the user's public key using Elliptic Curve
* Diffie Hellman cryptography over the prime256v1 curve.
*
* @return An Encrypted object containing the public key, salt, and
* ciphertext, which can be sent to the other party.
*/
public static Encrypted encrypt(PublicKey userPublicKey, byte[] payload) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException {
ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec("prime256v1");
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDH", "BC");
keyPairGenerator.initialize(parameterSpec);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
SecretKey secretKey = computeSecret(keyPair.getPrivate(), userPublicKey);
byte[] salt = generateSalt(16);
byte[] ciphertext = encrypt(secretKey, salt, payload);
return new Encrypted.Builder()
.withPublicKey(keyPair.getPublic())
.withSalt(salt)
.withCiphertext(ciphertext)
.build();
}
/**
* Compute the shared secret
*
* @param privateKey
* @param publicKey
* @return
* @throws NoSuchProviderException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
*/
public static SecretKey computeSecret(PrivateKey privateKey, PublicKey publicKey) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException {
KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH", "BC");
keyAgreement.init(privateKey);
keyAgreement.doPhase(publicKey, true);
return keyAgreement.generateSecret("AES");
}
/**
* Encrypt payload according to Encrypted Content-Encoding for HTTP. That
* is, use aesgcm-128 with given secret key and salt.
*
* @param secret
* @param salt
* @param payload
* @return
*/
public static byte[] encrypt(SecretKey secret, byte[] salt, byte[] payload) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException, BadPaddingException, IllegalBlockSizeException {
byte[] prk = hmacSha256(salt, secret.getEncoded());
byte[] key = hkdfExpand(prk, "Content-Encoding: aesgcm128".getBytes(), 16);
byte[] nonce = hkdfExpand(prk, "Content-Encoding: nonce".getBytes(), 12);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(16 * 8, nonce));
cipher.updateAAD(new byte[1]);
cipher.updateAAD(payload);
return cipher.doFinal();
}
/**
* Generate a random salt of length bytes
*
* @param length
* @return
*/
protected static byte[] generateSalt(int length) {
byte[] salt = new byte[length];
new Random().nextBytes(salt);
return salt;
}
/**
* Compute a 256-bit Hash-based Message Authentication Code (HMAC) using
* the SHA-256 cryptographic hash function.
*
* @param key
* @param data
* @return
*/
protected static byte[] hmacSha256(byte[] key, byte[] data) throws NoSuchAlgorithmException, InvalidKeyException {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key, "HmacSHA256"));
return mac.doFinal(data);
}
/**
* Compute the HMAC-based Key Derivation Function (HKDF) expansion as
* specified in RFC 5869 (https://tools.ietf.org/html/rfc5869).
*
* @param prk A pseudorandom key of at least HashLen octets
* @param info Context and application specific information
* @param length Length of output keying material in octets
* @return
*/
protected static byte[] hkdfExpand(byte[] prk, byte[] info, int length) throws InvalidKeyException, NoSuchAlgorithmException {
byte[] output = new byte[0];
byte[] T = new byte[0];
for (int i = 0; i < Math.ceil((double) length / 32); i++) {
T = hmacSha256(prk, Bytes.concat(T, info, new byte[]{(byte) (i + 1)}));
output = Bytes.concat(output, T);
}
return Arrays.copyOfRange(output, 0, length);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment