gpt4 book ai didi

java - 在 Java 中为 Web Push API 加密消息

转载 作者:太空狗 更新时间:2023-10-29 22:41:14 29 4
gpt4 key购买 nike

我正在尝试创建一个能够使用推送 API 发送推送消息的服务器:https://developer.mozilla.org/en-US/docs/Web/API/Push_API

我已经让客户端正常工作,但现在我希望能够从 Java 服务器发送带有负载的消息。

我看到了 nodejs 网络推送示例 ( https://www.npmjs.com/package/web-push ),但我无法将其正确转换为 Java。

我尝试按照示例使用此处找到的 DH key 交换:http://docs.oracle.com/javase/7/docs/technotes/guides/security/crypto/CryptoSpec.html#DH2Ex

在下面的 sheltond 的帮助下,我能够找出一些应该工作但没有工作的代码。

当我将加密消息发布到推送服务时,我得到了预期的 201 状态代码,但推送从未到达 Firefox。如果我删除有效负载和 header 并简单地向同一 URL 发送 POST 请求,则消息会成功到达 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”是浏览器中提供的 Push API 订阅方法的 key ,“PUSH_ENDPOINT_URL”是浏览器提供的推送端点。

如果我从成功的 nodejs 网络推送请求中获取值(密文、base64 bobPubKeyEnc 和盐)并用 Java 对其进行硬编码,它就可以工作。如果我将上面的代码与动态值一起使用,它将不起作用。

我确实注意到在 nodejs 实现中工作的密文总是比上面代码的 Java 密文大 1 个字节。我在这里使用的例子总是产生一个 81 字节的密文,但在 nodejs 中它总是 82 字节。这是否为我们提供了可能出错的线索?

我如何正确地加密负载以使其到达 Firefox?

在此先感谢您的帮助

最佳答案

根据 https://jrconlin.github.io/WebPushDataTestPage/ 更改代码后能够接收通知

在下面找到修改后的代码:

 

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECFieldFp;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.EllipticCurve;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;


public class WebPushEncryption {

private static final byte UNCOMPRESSED_POINT_INDICATOR = 0x04;
private static final ECParameterSpec params = new ECParameterSpec(
new EllipticCurve(new ECFieldFp(new BigInteger(
"FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF",
16)), new BigInteger(
"FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC",
16), new BigInteger(
"5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B",
16)), new ECPoint(new BigInteger(
"6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296",
16), new BigInteger(
"4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5",
16)), new BigInteger(
"FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551",
16), 1);

public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
String endpoint = "https://updates.push.services.mozilla.com/push/v1/xxx";
final byte[] alicePubKeyEnc = Base64.decode("base64 encoded public key ");

KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDH", "BC");
keyGen.initialize(params);

KeyPair bobKpair = keyGen.generateKeyPair();
PrivateKey localPrivateKey = bobKpair.getPrivate();
PublicKey localpublickey = bobKpair.getPublic();

final ECPublicKey remoteKey = fromUncompressedPoint(alicePubKeyEnc, params);

KeyAgreement bobKeyAgree = KeyAgreement.getInstance("ECDH", "BC");
bobKeyAgree.init(localPrivateKey);

byte[] bobPubKeyEnc = toUncompressedPoint((ECPublicKey) bobKpair.getPublic());

bobKeyAgree.doPhase(remoteKey, true);

SecretKey bobDesKey = bobKeyAgree.generateSecret("AES");

byte[] saltBytes = new byte[16];
new SecureRandom().nextBytes(saltBytes);

Mac extract = Mac.getInstance("HmacSHA256", "BC");
extract.init(new SecretKeySpec(saltBytes, "HmacSHA256"));
final byte[] prk = extract.doFinal(bobDesKey.getEncoded());

// Expand
Mac expand = Mac.getInstance("HmacSHA256", "BC");
expand.init(new SecretKeySpec(prk, "HmacSHA256"));

//aes algorithm
String info = "Content-Encoding: aesgcm128";
expand.update(info.getBytes(StandardCharsets.US_ASCII));
expand.update((byte) 1);
final byte[] key_bytes = expand.doFinal();
byte[] key_bytes16 = Arrays.copyOf(key_bytes, 16);
SecretKeySpec key = new SecretKeySpec(key_bytes16, 0, 16, "AES-GCM");

//nonce
expand.reset();
expand.init(new SecretKeySpec(prk, "HmacSHA256"));
String nonceinfo = "Content-Encoding: nonce";
expand.update(nonceinfo.getBytes(StandardCharsets.US_ASCII));
expand.update((byte) 1);
final byte[] nonce_bytes = expand.doFinal();
byte[] nonce_bytes12 = Arrays.copyOf(nonce_bytes, 12);

Cipher bobCipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");

byte[] iv = generateNonce(nonce_bytes12, 0);

bobCipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));

byte[] cleartext = ("{\n"
+ " \"message\" : \"great match41eeee!\",\n"
+ " \"title\" : \"Portugal vs. Denmark4255\",\n"
+ " \"icon\" : \"http://icons.iconarchive.com/icons/artdesigner/tweet-my-web/256/single-bird-icon.png\",\n"
+ " \"tag\" : \"testtag1\",\n"
+ " \"url\" : \"http://www.yahoo.com\"\n"
+ " }").getBytes();

byte[] cc = new byte[cleartext.length + 1];
cc[0] = 0;

for (int i = 0; i < cleartext.length; i++) {
cc[i + 1] = cleartext[i];
}

cleartext = cc;
byte[] ciphertext = bobCipher.doFinal(cleartext);

URL url = new URL(endpoint);
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=" + Base64.encode(bobPubKeyEnc));
urlConnection.setRequestProperty("encryption", "keyid=p256dh;salt=" + Base64.encode(saltBytes));
urlConnection.setRequestProperty("content-encoding", "aesgcm128");
urlConnection.setRequestProperty("ttl", "60");
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
final OutputStream outputStream = urlConnection.getOutputStream();
outputStream.write(ciphertext);
outputStream.flush();
outputStream.close();
if (urlConnection.getResponseCode() == 201) {
String result = readStream(urlConnection.getInputStream());
System.out.println("PUSH OK: " + result);
} else {
InputStream errorStream = urlConnection.getErrorStream();
String error = readStream(errorStream);
System.out.println("PUSH" + "Not OK: " + error);
}
}

static byte[] generateNonce(byte[] base, int index) {
byte[] nonce = Arrays.copyOfRange(base, 0, 12);

for (int i = 0; i < 6; ++i) {
nonce[nonce.length - 1 - i] ^= (byte) ((index / Math.pow(256, i))) & (0xff);
}
return nonce;
}

private static String readStream(InputStream errorStream) throws Exception {
BufferedInputStream bs = new BufferedInputStream(errorStream);
int i = 0;
byte[] b = new byte[1024];
StringBuilder sb = new StringBuilder();
while ((i = bs.read(b)) != -1) {
sb.append(new String(b, 0, i));
}

return sb.toString();
}

public static ECPublicKey fromUncompressedPoint(
final byte[] uncompressedPoint, final ECParameterSpec params)
throws Exception {

int offset = 0;
if (uncompressedPoint[offset++] != UNCOMPRESSED_POINT_INDICATOR) {
throw new IllegalArgumentException(
"Invalid uncompressedPoint encoding, no uncompressed point indicator");
}

int keySizeBytes = (params.getOrder().bitLength() + Byte.SIZE - 1)
/ Byte.SIZE;

if (uncompressedPoint.length != 1 + 2 * keySizeBytes) {
throw new IllegalArgumentException(
"Invalid uncompressedPoint encoding, not the correct size");
}

final BigInteger x = new BigInteger(1, Arrays.copyOfRange(
uncompressedPoint, offset, offset + keySizeBytes));
offset += keySizeBytes;
final BigInteger y = new BigInteger(1, Arrays.copyOfRange(
uncompressedPoint, offset, offset + keySizeBytes));
final ECPoint w = new ECPoint(x, y);
final ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(w, params);
final KeyFactory keyFactory = KeyFactory.getInstance("EC");
return (ECPublicKey) keyFactory.generatePublic(ecPublicKeySpec);
}

public static byte[] toUncompressedPoint(final ECPublicKey publicKey) {

int keySizeBytes = (publicKey.getParams().getOrder().bitLength() + Byte.SIZE - 1)
/ Byte.SIZE;

final byte[] uncompressedPoint = new byte[1 + 2 * keySizeBytes];
int offset = 0;
uncompressedPoint[offset++] = 0x04;

final byte[] x = publicKey.getW().getAffineX().toByteArray();
if (x.length <= keySizeBytes) {
System.arraycopy(x, 0, uncompressedPoint, offset + keySizeBytes
- x.length, x.length);
} else if (x.length == keySizeBytes + 1 && x[0] == 0) {
System.arraycopy(x, 1, uncompressedPoint, offset, keySizeBytes);
} else {
throw new IllegalStateException("x value is too large");
}
offset += keySizeBytes;

final byte[] y = publicKey.getW().getAffineY().toByteArray();
if (y.length <= keySizeBytes) {
System.arraycopy(y, 0, uncompressedPoint, offset + keySizeBytes
- y.length, y.length);
} else if (y.length == keySizeBytes + 1 && y[0] == 0) {
System.arraycopy(y, 1, uncompressedPoint, offset, keySizeBytes);
} else {
throw new IllegalStateException("y value is too large");
}

return uncompressedPoint;
}
}

关于java - 在 Java 中为 Web Push API 加密消息,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35228063/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com