在Java中生成VAPID密钥并将它们传递给JavaScript PushManager



我正在尝试在我的应用程序中将网络推送通知与网络推送协议一起使用。为了将推送API与VAPID一起使用,我需要applicationServerKey

PushManagersubscribe方法将 VAPID 密钥(仅公钥)作为参数,并将提供订阅终结点和推送消息的密钥。

为了生成VAPID密钥,我一直在使用node.js(谷歌web-push包)和openssl到目前为止。但是在我的用例中,VAPID密钥应该在Java中生成并传递给JavaScript以从浏览器订阅。

我正在尝试使用下面的 Java 代码来生成 VAPID 密钥。我能够成功创建密钥,但是当我传递生成的公钥(base64 编码的字符串)时,subscribe方法返回错误,指出:

无法注册服务辅助角色。DOMException:无法执行 在"PushManager"上"订阅":提供的应用程序服务器密钥不是 有效。。

请帮我解决这个问题。下面是我的Java代码:

ECNamedCurveParameterSpec parameterSpec = 
ECNamedCurveTable.getParameterSpec("prime256v1");
KeyPairGenerator keyPairGenerator = 
KeyPairGenerator.getInstance("ECDH", "BC");
keyPairGenerator.initialize(parameterSpec);
KeyPair serverKey = keyPairGenerator.generateKeyPair();
PrivateKey priv = serverKey.getPrivate();
PublicKey pub = serverKey.getPublic();`
System.out.println(Base64.toBase64String(pub.getEncoded()));

请参考下面的链接以获取MartijnDwars的答案。 https://github.com/web-push-libs/webpush-java/issues/30

您可以使用 Utils.savePublicKey 来转换 Java 生成的 公钥到一个字节[]。然后将此字节 [] 传递给 PushManager.subscribe 方法。

在 Java 中对 base64 对 byte[] 进行编码可能更方便,并且 base64 在 JavaScript 中解码字符串。例如,生成后 Java 中的密钥对:

KeyPair keyPair = generateKeyPair();
byte[] publicKey = Utils.savePublicKey((ECPublicKey) keyPair.getPublic());
String publicKeyBase64 = BaseEncoding.base64Url().encode(publicKey);
System.out.println("PublicKey = " + publicKeyBase64);
// PublicKey = BPf36QAqZNNvvnl9kkpTDerXUOt6Nm6P4x9GEvmFVFKgVyCVWy24KUTs6wLQtbV2Ug81utbNnx86_vZzXDyrl88=

然后在 JavaScript 中:

function subscribe() {
const publicKey = base64UrlToUint8Array('BPf36QAqZNNvvnl9kkpTDerXUOt6Nm6P4x9GEvmFVFKgVyCVWy24KUTs6wLQtbV2Ug81utbNnx86_vZzXDyrl88=');
navigator.serviceWorker.ready.then(function (serviceWorkerRegistration) {
serviceWorkerRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: publicKey
})
.then(function (subscription) {
return sendSubscriptionToServer(subscription);
})
.catch(function (e) {
if (Notification.permission === 'denied') {
console.warn('Permission for Notifications was denied');
} else {
console.error('Unable to subscribe to push.', e);
}
});
});
}
function base64UrlToUint8Array(base64UrlData) {
const padding = '='.repeat((4 - base64UrlData.length % 4) % 4);
const base64 = (base64UrlData + padding)
.replace(/-/g, '+')
.replace(/_/g, '/');
const rawData = atob(base64);
const buffer = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
buffer[i] = rawData.charCodeAt(i);
}
return buffer;
}

花了几个小时,我想我会分享我从收集几个网站中制定的解决方案,避免使用产生许多其他问题的Bouncy Castle。请尝试以下操作:

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec spec = new ECGenParameterSpec("secp256r1");
keyPairGenerator.initialize(spec, new SecureRandom()); 
KeyPair keyPair = keyPairGenerator.generateKeyPair();
ECPublicKey publicKey = (ECPublicKey)   keyPair.getPublic();
ECPoint ecp = publicKey.getW();
byte[] x = ecp.getAffineX().toByteArray();
byte[] y = ecp.getAffineY().toByteArray();
// Convert 04 to bytes
String s= "04";
int len = s.length();
byte[] firstBit = new byte[len / 2];
for (int i = 0; i < len; i += 2) 
{
firstBit[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + 
Character.digit(s.charAt(i+1), 16));
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream( );
outputStream.write(firstBit);
outputStream.write(x);
outputStream.write(y);

publicKeyBytes = outputStream.toByteArray( );
Base64 encoder = new Base64(-1,null,true);
byte[] encodedBytes = encoder.encode(publicKeyBytes);
String publicKeyBase64 = new String(encodedBytes, StandardCharsets.UTF_8);

使用赏金城堡,您可以使用以下代码生成 vapid 密钥:

ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec("prime256v1");
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDH", "BC");
keyPairGenerator.initialize(parameterSpec);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
String publicKeyString = Base64.getUrlEncoder().withoutPadding().encodeToString(publicKey.getQ().getEncoded(false));
System.out.println(publicKeyString);
String privateKeyString = Base64.getUrlEncoder().withoutPadding().encodeToString(privateKey.getD().toByteArray());
System.out.println(privateKeyString);

这是一个处理 BigIntegers 随机长度的糟糕 Java 8 的解决方案。

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( "EC" );
keyPairGenerator.initialize( new ECGenParameterSpec( "secp256r1" ), new SecureRandom() );
KeyPair keyPair = keyPairGenerator.generateKeyPair();
ECPublicKey publicKey = (ECPublicKey)keyPair.getPublic();
ECPoint ecp = publicKey.getW();
byte[] applicationServerKey = new byte[65];
applicationServerKey[0] = 4;
// copy getAffineX() to the target
byte[] affine = ecp.getAffineX().toByteArray(); // typical 31 to 33 bytes
int pos = 1;
int off, length;
off = affine.length - 32;
if( off >=  0 ) {
// there are leading zero values which we cut
length = 32;
} else {
pos -= off;
length = 32 + off;
off = 0;
}
System.arraycopy( affine, off, applicationServerKey, pos, length );
// copy getAffineY() to the target
affine = ecp.getAffineY().toByteArray(); // typical 31 to 33 bytes
pos = 33;
off = affine.length - 32;
if( off >=  0 ) {
// there are leading zero values which we cut
length = 32;
} else {
pos -= off;
length = 32 + off;
off = 0;
}
System.arraycopy( affine, off, applicationServerKey, pos, length );
return Base64.getEncoder().encodeToString( applicationServerKey );

或者更简单的Java内置编码:

ECPublicKey publicKey = ...
byte[] keyBytes = publicKey.getEncoded();
// 26 -> X509 overhead, length ever 91, results in 65 bytes
keyBytes = Arrays.copyOfRange( keyBytes, 26, 91 ); 
return this.applicationServerKey = Base64.getEncoder().encodeToString( keyBytes );

相关内容

最新更新