我正在尝试创建一个能够使用 Push API 发送推送消息的服务器:https ://developer.mozilla.org/en-US/docs/Web/API/Push_API
我已经让客户端工作了,但现在我希望能够从 Java 服务器发送带有有效负载的消息。
我看到了 nodejs web-push 示例(https://www.npmjs.com/package/web-push),但我无法将其正确转换为 Java。
我尝试按照示例使用此处找到的 DH 密钥交换:http: //docs.oracle.com/javase/7/docs/technotes/guides/security/crypto/CryptoSpec.html#DH2Ex
在下面的 Sheltond 的帮助下,我能够找出一些应该可以工作但没有工作的代码。
当我将加密消息发布到推送服务时,我得到了预期的 201 状态代码,但推送从未到达 Firefox。如果我删除有效负载和标头并简单地将 POST 请求发送到相同的 URL,则消息成功到达 Firefox,但没有数据。我怀疑这可能与我使用 Cipher.getInstance("AES/GCM/NoPadding"); 加密数据的方式有关。
这是我目前使用的代码:
try {
final byte[] alicePubKeyEnc = Util.fromBase64("BASE_64_PUBLIC_KEY_FROM_PUSH_SUBSCRIPTION");
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec kpgparams = new ECGenParameterSpec("secp256r1");
kpg.initialize(kpgparams);
ECParameterSpec params = ((ECPublicKey) kpg.generateKeyPair().getPublic()).getParams();
final ECPublicKey alicePubKey = fromUncompressedPoint(alicePubKeyEnc, params);
KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("EC");
bobKpairGen.initialize(params);
KeyPair bobKpair = bobKpairGen.generateKeyPair();
KeyAgreement bobKeyAgree = KeyAgreement.getInstance("ECDH");
bobKeyAgree.init(bobKpair.getPrivate());
byte[] bobPubKeyEnc = toUncompressedPoint((ECPublicKey) bobKpair.getPublic());
bobKeyAgree.doPhase(alicePubKey, true);
Cipher bobCipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKey bobDesKey = bobKeyAgree.generateSecret("AES");
byte[] saltBytes = new byte[16];
new SecureRandom().nextBytes(saltBytes);
Mac extract = Mac.getInstance("HmacSHA256");
extract.init(new SecretKeySpec(saltBytes, "HmacSHA256"));
final byte[] prk = extract.doFinal(bobDesKey.getEncoded());
// Expand
Mac expand = Mac.getInstance("HmacSHA256");
expand.init(new SecretKeySpec(prk, "HmacSHA256"));
String info = "Content-Encoding: aesgcm128";
expand.update(info.getBytes(StandardCharsets.US_ASCII));
expand.update((byte) 1);
final byte[] key_bytes = expand.doFinal();
// Use the result
SecretKeySpec key = new SecretKeySpec(key_bytes, 0, 16, "AES");
bobCipher.init(Cipher.ENCRYPT_MODE, key);
byte[] cleartext = "{\"this\":\"is a test that is supposed to be working but it is not\"}".getBytes();
byte[] ciphertext = bobCipher.doFinal(cleartext);
URL url = new URL("PUSH_ENDPOINT_URL");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("Content-Length", ciphertext.length + "");
urlConnection.setRequestProperty("Content-Type", "application/octet-stream");
urlConnection.setRequestProperty("Encryption-Key", "keyid=p256dh;dh=" + Util.toBase64UrlSafe(bobPubKeyEnc));
urlConnection.setRequestProperty("Encryption", "keyid=p256dh;salt=" + Util.toBase64UrlSafe(saltBytes));
urlConnection.setRequestProperty("Content-Encoding", "aesgcm128");
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
final OutputStream outputStream = urlConnection.getOutputStream();
outputStream.write(ciphertext);
outputStream.flush();
outputStream.close();
if (urlConnection.getResponseCode() == 201) {
String result = Util.readStream(urlConnection.getInputStream());
Log.v("PUSH", "OK: " + result);
} else {
InputStream errorStream = urlConnection.getErrorStream();
String error = Util.readStream(errorStream);
Log.v("PUSH", "Not OK: " + error);
}
} catch (Exception e) {
Log.v("PUSH", "Not OK: " + e.toString());
}
其中“BASE_64_PUBLIC_KEY_FROM_PUSH_SUBSCRIPTION”是提供的浏览器中推送 API 订阅方法的密钥,“PUSH_ENDPOINT_URL”是浏览器提供的推送端点。
如果我从成功的 nodejs web-push 请求中获取值(密文、base64 bobPubKeyEnc 和 salt)并用 Java 对它们进行硬编码,它就可以工作。如果我将上面的代码与动态值一起使用,它将不起作用。
我确实注意到,在 nodejs 实现中工作的密文总是比上面代码的 Java 密文大 1 个字节。我在这里使用的示例总是产生一个 81 字节的密文,但在 nodejs 中它总是 82 字节。这是否为我们提供了可能出现问题的线索?
如何正确加密有效负载以使其到达 Firefox?
提前感谢您的帮助