WebPushHybridEncrypt implements HybridEncrypt { @Override public byte[] encrypt(final byte[] plaintext, final byte[] contextInfo ) throws GeneralSecurityException { if (contextInfo != null) { throw new GeneralSecurityException("contextInfo must be null because it is unused"); } if (plaintext.length > recordSize - WebPushConstants.CIPHERTEXT_OVERHEAD) { throw new GeneralSecurityException("plaintext too long"); } KeyPair keyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE); ECPrivateKey ephemeralPrivateKey = (ECPrivateKey) keyPair.getPrivate(); ECPublicKey ephemeralPublicKey = (ECPublicKey) keyPair.getPublic(); byte[] ecdhSecret = EllipticCurves.computeSharedSecret(ephemeralPrivateKey, recipientPublicPoint); byte[] ephemeralPublicKeyBytes = EllipticCurves.pointEncode( WebPushConstants.NIST_P256_CURVE_TYPE, WebPushConstants.UNCOMPRESSED_POINT_FORMAT, ephemeralPublicKey.getW()); byte[] ikm = WebPushUtil.computeIkm(ecdhSecret, authSecret, recipientPublicKey, ephemeralPublicKeyBytes); byte[] salt = Random.randBytes(WebPushConstants.SALT_SIZE); byte[] cek = WebPushUtil.computeCek(ikm, salt); byte[] nonce = WebPushUtil.computeNonce(ikm, salt); return ByteBuffer.allocate(WebPushConstants.CIPHERTEXT_OVERHEAD + plaintext.length) .put(salt) .putInt(recordSize) .put((byte) WebPushConstants.PUBLIC_KEY_SIZE) .put(ephemeralPublicKeyBytes) .put(encrypt(cek, nonce, plaintext)) .array(); } private  WebPushHybridEncrypt(Builder builder); @Override byte[] encrypt(final byte[] plaintext, final byte[] contextInfo /* unused */);  }
@Test public void testEncryptDecrypt() throws Exception { KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE); ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate(); ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic(); byte[] uaPublicKeyBytes = EllipticCurves.pointEncode( WebPushConstants.NIST_P256_CURVE_TYPE, WebPushConstants.UNCOMPRESSED_POINT_FORMAT, uaPublicKey.getW()); byte[] authSecret = Random.randBytes(16); HybridEncrypt hybridEncrypt = new WebPushHybridEncrypt.Builder() .withAuthSecret(authSecret) .withRecipientPublicKey(uaPublicKeyBytes) .build(); HybridDecrypt hybridDecrypt = new WebPushHybridDecrypt.Builder() .withAuthSecret(authSecret) .withRecipientPublicKey(uaPublicKeyBytes) .withRecipientPrivateKey(uaPrivateKey) .build(); Set<String> salts = new TreeSet<>(); Set<String> ephemeralPublicKeys = new TreeSet<>(); Set<String> payloads = new TreeSet<>(); int numTests = 100; if (TestUtil.isTsan()) { numTests = 5; } for (int j = 0; j < numTests; j++) { byte[] plaintext = Random.randBytes(j); byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null ); assertEquals(ciphertext.length, plaintext.length + WebPushConstants.CIPHERTEXT_OVERHEAD); assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null )); ByteBuffer record = ByteBuffer.wrap(ciphertext); byte[] salt = new byte[WebPushConstants.SALT_SIZE]; record.get(salt); salts.add(Hex.encode(salt)); int unused1 = record.getInt(); int unused2 = (int) record.get(); byte[] ephemeralPublicKey = new byte[WebPushConstants.PUBLIC_KEY_SIZE]; record.get(ephemeralPublicKey); ephemeralPublicKeys.add(Hex.encode(ephemeralPublicKey)); byte[] payload = new byte[ciphertext.length - WebPushConstants.CONTENT_CODING_HEADER_SIZE]; record.get(payload); payloads.add(Hex.encode(payload)); } assertEquals(numTests, salts.size()); assertEquals(numTests, ephemeralPublicKeys.size()); assertEquals(numTests, payloads.size()); }
@Test public void testEncryptDecryptWithVaryingRecordSizes() throws Exception { KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE); ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate(); ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic(); byte[] authSecret = Random.randBytes(16); int numTests = 100; if (TestUtil.isTsan()) { numTests = 5; } for (int i = 0; i < numTests; i++) { int recordSize = WebPushConstants.CIPHERTEXT_OVERHEAD + Random.randInt( WebPushConstants.MAX_CIPHERTEXT_SIZE - WebPushConstants.CIPHERTEXT_OVERHEAD); HybridEncrypt hybridEncrypt = new WebPushHybridEncrypt.Builder() .withRecordSize(recordSize) .withAuthSecret(authSecret) .withRecipientPublicKey(uaPublicKey) .build(); HybridDecrypt hybridDecrypt = new WebPushHybridDecrypt.Builder() .withRecordSize(recordSize) .withAuthSecret(authSecret) .withRecipientPublicKey(uaPublicKey) .withRecipientPrivateKey(uaPrivateKey) .build(); byte[] plaintext = Random.randBytes(recordSize - WebPushConstants.CIPHERTEXT_OVERHEAD); byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null ); assertEquals(ciphertext.length, plaintext.length + WebPushConstants.CIPHERTEXT_OVERHEAD); assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null )); } }
@Test public void testEncryptDecrypt_largestPossibleRecordSize() throws Exception { KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE); ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate(); ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic(); byte[] authSecret = Random.randBytes(16); HybridEncrypt hybridEncrypt = new WebPushHybridEncrypt.Builder() .withRecordSize(WebPushConstants.MAX_CIPHERTEXT_SIZE) .withAuthSecret(authSecret) .withRecipientPublicKey(uaPublicKey) .build(); HybridDecrypt hybridDecrypt = new WebPushHybridDecrypt.Builder() .withRecordSize(WebPushConstants.MAX_CIPHERTEXT_SIZE) .withAuthSecret(authSecret) .withRecipientPublicKey(uaPublicKey) .withRecipientPrivateKey(uaPrivateKey) .build(); byte[] plaintext = Random.randBytes( WebPushConstants.MAX_CIPHERTEXT_SIZE - WebPushConstants.CIPHERTEXT_OVERHEAD); byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null ); assertEquals(ciphertext.length, plaintext.length + WebPushConstants.CIPHERTEXT_OVERHEAD); assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null )); }
@Test public void testEncryptDecrypt_smallestPossibleRecordSize() throws Exception { KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE); ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate(); ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic(); byte[] authSecret = Random.randBytes(16); HybridEncrypt hybridEncrypt = new WebPushHybridEncrypt.Builder() .withRecordSize(WebPushConstants.CIPHERTEXT_OVERHEAD) .withAuthSecret(authSecret) .withRecipientPublicKey(uaPublicKey) .build(); HybridDecrypt hybridDecrypt = new WebPushHybridDecrypt.Builder() .withRecordSize(WebPushConstants.CIPHERTEXT_OVERHEAD) .withAuthSecret(authSecret) .withRecipientPublicKey(uaPublicKey) .withRecipientPrivateKey(uaPrivateKey) .build(); byte[] plaintext = new byte[0]; byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null ); assertEquals(ciphertext.length, plaintext.length + WebPushConstants.CIPHERTEXT_OVERHEAD); assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null )); }
@Test public void testNonNullContextInfo() throws Exception { KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE); ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic(); byte[] authSecret = Random.randBytes(16); HybridEncrypt hybridEncrypt = new WebPushHybridEncrypt.Builder() .withAuthSecret(authSecret) .withRecipientPublicKey(uaPublicKey) .build(); byte[] plaintext = Random.randBytes(20); byte[] contextInfo = new byte[0]; try { byte[] unusedCiphertext = hybridEncrypt.encrypt(plaintext, contextInfo); fail("Expected GeneralSecurityException"); } catch (GeneralSecurityException ex) { } }