使用JavaScript中的EDDSA算法签署消息以获取JWT

发布于 2025-01-21 21:23:51 字数 2384 浏览 4 评论 0原文

我需要使用EDDSA算法获得JWT才能使用API​​。我有一个私钥可以签署消息,可以在下一个库中使用PHP来做到这一点: https:/https:/ /Github.com/firebase/php-jwt (您可以在readme看到EDDSA的示例)。现在我需要在JS中进行同样的操作,但是我找不到使用给定的秘密键(编码基础64)获得JWT的方法(只有一个示例不是真正的SecretKey):

const secretKey = Dm2xriMD6riJagld4WCA6zWqtuWh40UzT/ZKO0pZgtHATOt0pGw90jG8BQHCE3EOjiCkFR2/gaW6JWi+3nZp8A==

我尝试了很多像这样的库Jose,JS-NACL,Crypto,libsodium等。我真的很接近使用libodium库获得JWT,现在我附加了代码:

const base64url = require("base64url");
const _sodium = require("libsodium-wrappers");
const moment = require("moment");

const getJWT = async () => {
  await _sodium.ready;
  const sodium = _sodium;

  const privateKey =
    "Dm2xriMD6riJagld4WCA6zWqtuWh40UzT/ZKO0pZgtHATOt0pGw90jG8BQHCE3EOjiCkFR2/gaW6JWi+3nZp8A==";
  const payload = {
    iss: "test",
    aud: "test.com",
    iat: 1650101178,
    exp: 1650101278,
    sub: "12345678-1234-1234-1234-123456789123"
  };
  const { msg, keyAscii} = encode(payload, privateKey, "EdDSA");
  const signature = sodium.crypto_sign_detached(msg, keyDecoded); //returns Uint8Array(64)
  //Here is the problem.
};
const encode = (payload, key, alg) => {
  const header = {
    typ: "JWT",
    alg //'EdDSA'
  };
  const headerBase64URL = base64url(JSON.stringify(header));
  const payloadBase64URL = base64url(JSON.stringify(payload));
  const headerAndPayloadBase64URL = `${headerBase64URL}.${payloadBase64URL}`;
  const keyAscii= Buffer.from(key, "base64").toString("ascii");
  return {headerAndPayloadBase64URL , keyAscii}
};

问题是在钠中我需要这样的JWT:

eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJ0ZXN0IiwiYXVkIjoidGVzdC5jb20iLCJpYXQiOjE2NTAxMDExNzgsImV4cCI6MTY1MDEwMTI3OCwic3ViIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDU2Nzg5MTIzIn0.f7WG_02UKljrMeVVOTNNBAGxtLXJUT_8QAnujNhomV18Pn5cU-0lHRgVlmRttOlqI7Iol_fHut3C4AOXxDGnAQ

如何更改Uint8array(64)以正确格式获得签名以获取JWT?我尝试使用base64,base64url,十六进制,文本,ASCII等尝试,最终的JWT无效(因为签名是错误的)。 如果将我的代码与我提到的PHP的代码进行比较,则非常相似,但是函数钠.crypto_sign_detached returns返回uint8array(64)在JS库中,并且PHP中的相同函数返回字符串,我可以获取令牌。 或者,也许有一种方法可以调整我给定的私钥在其他库中使用(例如我收到私有密钥格式错误的加密货币或何塞) 谢谢你!

I need to get JWT with EdDSA algorithm to be able to use an API. I have the private key to sign the message and I could do that with PHP with the next library: https://github.com/firebase/php-jwt (you can see the example with EdDSA at README). Now I need to do the same in JS but I didn't find the way to get JWT with a given secret key (encoded base 64) like that (only an example is not the real secretKey):

const secretKey = Dm2xriMD6riJagld4WCA6zWqtuWh40UzT/ZKO0pZgtHATOt0pGw90jG8BQHCE3EOjiCkFR2/gaW6JWi+3nZp8A==

I tried a lot of libraries like jose, js-nacl, crypto, libsodium, etc. And I am really close to get the JWT with libsodium library, now I attach the code:

const base64url = require("base64url");
const _sodium = require("libsodium-wrappers");
const moment = require("moment");

const getJWT = async () => {
  await _sodium.ready;
  const sodium = _sodium;

  const privateKey =
    "Dm2xriMD6riJagld4WCA6zWqtuWh40UzT/ZKO0pZgtHATOt0pGw90jG8BQHCE3EOjiCkFR2/gaW6JWi+3nZp8A==";
  const payload = {
    iss: "test",
    aud: "test.com",
    iat: 1650101178,
    exp: 1650101278,
    sub: "12345678-1234-1234-1234-123456789123"
  };
  const { msg, keyAscii} = encode(payload, privateKey, "EdDSA");
  const signature = sodium.crypto_sign_detached(msg, keyDecoded); //returns Uint8Array(64)
  //Here is the problem.
};
const encode = (payload, key, alg) => {
  const header = {
    typ: "JWT",
    alg //'EdDSA'
  };
  const headerBase64URL = base64url(JSON.stringify(header));
  const payloadBase64URL = base64url(JSON.stringify(payload));
  const headerAndPayloadBase64URL = `${headerBase64URL}.${payloadBase64URL}`;
  const keyAscii= Buffer.from(key, "base64").toString("ascii");
  return {headerAndPayloadBase64URL , keyAscii}
};

The problem is in the sodium.crypto_sign_detached function because it returns an Uint8Array(64) signature and and I need the JWT like that:

eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJ0ZXN0IiwiYXVkIjoidGVzdC5jb20iLCJpYXQiOjE2NTAxMDExNzgsImV4cCI6MTY1MDEwMTI3OCwic3ViIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDU2Nzg5MTIzIn0.f7WG_02UKljrMeVVOTNNBAGxtLXJUT_8QAnujNhomV18Pn5cU-0lHRgVlmRttOlqI7Iol_fHut3C4AOXxDGnAQ

How can I change the Uint8Array(64) to get the signature in a right format to get the JWT? I tried with base64, base64url, hex, text, ascii, etc and the final JWT is not valid (because the signature is wrong).
If you compare my code with the code that I mentioned with PHP is very similar but the function sodium.crypto_sign_detached returns Uint8Array(64) at JS library and the same function in PHP returns an string and I can get the token.
Or maybe there a way to adapt my given private key for use in other library (like crypto or jose where I received an error for the private key format)
Thank you!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

在发布的nodejs代码中,有以下问题:

  • crypto_sign_detached()将签名返回为uint8array,可以用buffer.from()代码>并将其转换为带有base64url()的base64字符串。
  • 串联headerandpayloadbase64url和base64url编码的签名与作为分隔器给您您正在寻找的JWT。
  • 原始的私钥不得用'ascii'解码,因为这通常会损坏数据。相反,它应该简单地作为缓冲区处理。注意:如果由于某些原因需要转换为字符串,请使用'binary'作为编码,该编码会产生字节字符串(但是,这不是crypto_sign_detached()由于此功能期望缓冲区)。

通过这些更改,以下Nodejs代码结果:

const _sodium = require('libsodium-wrappers');
const base64url = require("base64url");

const getJWT = async () => {  
    await _sodium.ready;
    const sodium = _sodium;
    const privateKey = "Dm2xriMD6riJagld4WCA6zWqtuWh40UzT/ZKO0pZgtHATOt0pGw90jG8BQHCE3EOjiCkFR2/gaW6JWi+3nZp8A==";
    const payload = {
        iss: "test",
        aud: "test.com",
        iat: 1650101178,
        exp: 1650101278,
        sub: "12345678-1234-1234-1234-123456789123"
     };  
     const {headerAndPayloadBase64URL, keyBuf} = encode(payload, privateKey, "EdDSA");
     const signature = sodium.crypto_sign_detached(headerAndPayloadBase64URL, keyBuf); 
     const signatureBase64url = base64url(Buffer.from(signature));
     console.log(`${headerAndPayloadBase64URL}.${signatureBase64url}`) // eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJ0ZXN0IiwiYXVkIjoidGVzdC5jb20iLCJpYXQiOjE2NTAxMDExNzgsImV4cCI6MTY1MDEwMTI3OCwic3ViIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDU2Nzg5MTIzIn0.f7WG_02UKljrMeVVOTNNBAGxtLXJUT_8QAnujNhomV18Pn5cU-0lHRgVlmRttOlqI7Iol_fHut3C4AOXxDGnAQ
};

const encode = (payload, key, alg) => {
    const header = {
        typ: "JWT",
        alg //'EdDSA'
    };
    const headerBase64URL = base64url(JSON.stringify(header));
    const payloadBase64URL = base64url(JSON.stringify(payload));
    const headerAndPayloadBase64URL = `${headerBase64URL}.${payloadBase64URL}`;
    const keyBuf = Buffer.from(key, "base64");
    return {headerAndPayloadBase64URL, keyBuf};
};

getJWT();

测试:
由于ED25519是确定性的,因此可以通过比较两个JWT来检查NodeJS代码:如果与上述NodeJS代码一样,则使用相同的标头和有效载荷,与PHP代码相同的签名和相同的相同 JWT是通过PHP代码生成的,即:

eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJ0ZXN0IiwiYXVkIjoidGVzdC5jb20iLCJpYXQiOjE2NTAxMDExNzgsImV4cCI6MTY1MDEwMTI3OCwic3ViIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDU2Nzg5MTIzIn0.f7WG_02UKljrMeVVOTNNBAGxtLXJUT_8QAnujNhomV18Pn5cU-0lHRgVlmRttOlqI7Iol_fHut3C4AOXxDGnAQ

表明NodeJS代码有效。


请注意,可以使用 package而不是mongm date.now()。这将返回以毫秒为单位的时间,因此该值必须除以1000,例如Math.Round(date.now()/1000),但保留了依赖关系。

In the posted NodeJS code there are the following issues:

  • crypto_sign_detached() returns the signature as a Uint8Array, which can be imported with Buffer.from() and converted to a Base64 string with base64url().
  • Concatenating headerAndPayloadBase64URL and the Base64url encoded signature with a . as separator gives the JWT you are looking for.
  • The raw private key must not be decoded with 'ascii', as this generally corrupts the data. Instead, it should simply be handled as buffer. Note: If for some reason a conversion to a string is required, use 'binary' as encoding, which produces a byte string (however, this is not an option with crypto_sign_detached() as this function expects a buffer).

With these changes, the following NodeJS code results:

const _sodium = require('libsodium-wrappers');
const base64url = require("base64url");

const getJWT = async () => {  
    await _sodium.ready;
    const sodium = _sodium;
    const privateKey = "Dm2xriMD6riJagld4WCA6zWqtuWh40UzT/ZKO0pZgtHATOt0pGw90jG8BQHCE3EOjiCkFR2/gaW6JWi+3nZp8A==";
    const payload = {
        iss: "test",
        aud: "test.com",
        iat: 1650101178,
        exp: 1650101278,
        sub: "12345678-1234-1234-1234-123456789123"
     };  
     const {headerAndPayloadBase64URL, keyBuf} = encode(payload, privateKey, "EdDSA");
     const signature = sodium.crypto_sign_detached(headerAndPayloadBase64URL, keyBuf); 
     const signatureBase64url = base64url(Buffer.from(signature));
     console.log(`${headerAndPayloadBase64URL}.${signatureBase64url}`) // eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJ0ZXN0IiwiYXVkIjoidGVzdC5jb20iLCJpYXQiOjE2NTAxMDExNzgsImV4cCI6MTY1MDEwMTI3OCwic3ViIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDU2Nzg5MTIzIn0.f7WG_02UKljrMeVVOTNNBAGxtLXJUT_8QAnujNhomV18Pn5cU-0lHRgVlmRttOlqI7Iol_fHut3C4AOXxDGnAQ
};

const encode = (payload, key, alg) => {
    const header = {
        typ: "JWT",
        alg //'EdDSA'
    };
    const headerBase64URL = base64url(JSON.stringify(header));
    const payloadBase64URL = base64url(JSON.stringify(payload));
    const headerAndPayloadBase64URL = `${headerBase64URL}.${payloadBase64URL}`;
    const keyBuf = Buffer.from(key, "base64");
    return {headerAndPayloadBase64URL, keyBuf};
};

getJWT();

Test:
Since Ed25519 is deterministic, the NodeJS code can be checked by comparing both JWTs: If, as in the above NodeJS code, the same header and payload are used as in the PHP code, the same signature and thus the same JWT is generated as by the PHP code, namely:

eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJ0ZXN0IiwiYXVkIjoidGVzdC5jb20iLCJpYXQiOjE2NTAxMDExNzgsImV4cCI6MTY1MDEwMTI3OCwic3ViIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDU2Nzg5MTIzIn0.f7WG_02UKljrMeVVOTNNBAGxtLXJUT_8QAnujNhomV18Pn5cU-0lHRgVlmRttOlqI7Iol_fHut3C4AOXxDGnAQ

which shows that the NodeJS code works.


Note that instead of the moment package, Date.now() could be used. This will return the time in milliseconds, so the value has to be divided by 1000, e.g. Math.round(Date.now()/1000), but saves a dependency.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文