Created
March 6, 2016 21:51
-
-
Save martijndwars/36b6a1f5d9441f6e1e65 to your computer and use it in GitHub Desktop.
Web push in Java
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
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); | |
} | |
} | |
} |
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
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