【问题标题】:Generate a VAPID keys in Java and pass them to JavaScript PushManager在 Java 中生成一个 VAPID 密钥并将它们传递给 JavaScript PushManager
【发布时间】:2017-05-24 05:23:24
【问题描述】:

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

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

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

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

无法注册服务工作者。 DOMException: 执行失败 'PushManager' 上的'subscribe':提供的 applicationServerKey 不是 有效..

请帮我解决这个问题。以下是我的 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()));

【问题讨论】:

  • 嗨 - 运气好吗?你也可以给我一些指点吗?

标签: java bouncycastle web-push ecdsa vapid


【解决方案1】:

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

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

在 Java 中对 byte[] 进行 base64 编码可能更方便 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;
}

【讨论】:

【解决方案2】:

在这方面花了几个小时,我想我会分享我从几个网站收集到的解决方案,避免使用会产生许多其他问题的 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);

【讨论】:

  • 为了完整性:我相信这里使用的 Base64 类来自 org.apache.commons.codec
【解决方案3】:

使用 bountycastle,您可以使用以下代码生成 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);

【讨论】:

    【解决方案4】:

    这是处理 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 );
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-07-25
      • 1970-01-01
      • 2021-01-20
      • 1970-01-01
      • 1970-01-01
      • 2016-10-10
      • 2019-06-06
      • 2021-01-06
      相关资源
      最近更新 更多