在Javascript使用EdDSA算法签名消息获取JWT
Sign a message with EdDSA algorithm in Javascript to get JWT
我需要使用 EdDSA 算法获取 JWT 才能使用 API。我有私钥来签署消息,我可以用 PHP 和下一个库来做到这一点:https://github.com/firebase/php-jwt(你可以在 README 中看到 EdDSA 的例子)。现在我需要在 JS 中做同样的事情,但我没有找到使用给定密钥(编码的 base 64)获取 JWT 的方法(仅示例不是真正的密钥):
const secretKey = Dm2xriMD6riJagld4WCA6zWqtuWh40UzT/ZKO0pZgtHATOt0pGw90jG8BQHCE3EOjiCkFR2/gaW6JWi+3nZp8A==
我尝试了很多库,如 jose、js-nacl、crypto、libsodium 等。我真的很接近使用 libsodium 库获得 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}
};
问题出在 sodium.crypto_sign_detached 函数中,因为它 returns 一个 Uint8Array(64) 签名,我需要这样的 JWT:
eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJ0ZXN0IiwiYXVkIjoidGVzdC5jb20iLCJpYXQiOjE2NTAxMDExNzgsImV4cCI6MTY1MDEwMTI3OCwic3ViIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDU2Nzg5MTIzIn0.f7WG_02UKljrMeVVOTNNBAGxtLXJUT_8QAnujNhomV18Pn5cU-0lHRgVlmRttOlqI7Iol_fHut3C4AOXxDGnAQ
如何更改 Uint8Array(64) 以获取正确格式的签名以获取 JWT?我尝试使用 base64、base64url、hex、text、ascii 等,但最终的 JWT 无效(因为签名错误)。
如果您将我的代码与我在 PHP 中提到的代码进行比较,但是 JS 库中的函数 sodium.crypto_sign_detached returns Uint8Array(64) 和 PHP 中的函数相同returns 一个字符串,我可以获得令牌。
或者也许有一种方法可以调整我给定的私钥以用于其他库(例如 crypto 或 jose,我收到了私钥格式错误)
谢谢!
在发布的 NodeJS 代码中存在以下问题:
crypto_sign_detached()
returns 签名为 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 代码中相同的 header 和有效载荷,则相同的签名和因此 same JWT 是由 PHP 代码生成的,即:
eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJ0ZXN0IiwiYXVkIjoidGVzdC5jb20iLCJpYXQiOjE2NTAxMDExNzgsImV4cCI6MTY1MDEwMTI3OCwic3ViIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDU2Nzg5MTIzIn0.f7WG_02UKljrMeVVOTNNBAGxtLXJUT_8QAnujNhomV18Pn5cU-0lHRgVlmRttOlqI7Iol_fHut3C4AOXxDGnAQ
这表明 NodeJS 代码有效。
请注意,可以使用 Date.now()
而不是 moment
包。这将 return 以毫秒为单位的时间,因此该值必须除以 1000,例如Math.round(Date.now()/1000)
,但是保存了一个依赖。
我需要使用 EdDSA 算法获取 JWT 才能使用 API。我有私钥来签署消息,我可以用 PHP 和下一个库来做到这一点:https://github.com/firebase/php-jwt(你可以在 README 中看到 EdDSA 的例子)。现在我需要在 JS 中做同样的事情,但我没有找到使用给定密钥(编码的 base 64)获取 JWT 的方法(仅示例不是真正的密钥):
const secretKey = Dm2xriMD6riJagld4WCA6zWqtuWh40UzT/ZKO0pZgtHATOt0pGw90jG8BQHCE3EOjiCkFR2/gaW6JWi+3nZp8A==
我尝试了很多库,如 jose、js-nacl、crypto、libsodium 等。我真的很接近使用 libsodium 库获得 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}
};
问题出在 sodium.crypto_sign_detached 函数中,因为它 returns 一个 Uint8Array(64) 签名,我需要这样的 JWT:
eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJ0ZXN0IiwiYXVkIjoidGVzdC5jb20iLCJpYXQiOjE2NTAxMDExNzgsImV4cCI6MTY1MDEwMTI3OCwic3ViIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDU2Nzg5MTIzIn0.f7WG_02UKljrMeVVOTNNBAGxtLXJUT_8QAnujNhomV18Pn5cU-0lHRgVlmRttOlqI7Iol_fHut3C4AOXxDGnAQ
如何更改 Uint8Array(64) 以获取正确格式的签名以获取 JWT?我尝试使用 base64、base64url、hex、text、ascii 等,但最终的 JWT 无效(因为签名错误)。 如果您将我的代码与我在 PHP 中提到的代码进行比较,但是 JS 库中的函数 sodium.crypto_sign_detached returns Uint8Array(64) 和 PHP 中的函数相同returns 一个字符串,我可以获得令牌。 或者也许有一种方法可以调整我给定的私钥以用于其他库(例如 crypto 或 jose,我收到了私钥格式错误) 谢谢!
在发布的 NodeJS 代码中存在以下问题:
crypto_sign_detached()
returns 签名为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 代码中相同的 header 和有效载荷,则相同的签名和因此 same JWT 是由 PHP 代码生成的,即:
eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJ0ZXN0IiwiYXVkIjoidGVzdC5jb20iLCJpYXQiOjE2NTAxMDExNzgsImV4cCI6MTY1MDEwMTI3OCwic3ViIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDU2Nzg5MTIzIn0.f7WG_02UKljrMeVVOTNNBAGxtLXJUT_8QAnujNhomV18Pn5cU-0lHRgVlmRttOlqI7Iol_fHut3C4AOXxDGnAQ
这表明 NodeJS 代码有效。
请注意,可以使用 Date.now()
而不是 moment
包。这将 return 以毫秒为单位的时间,因此该值必须除以 1000,例如Math.round(Date.now()/1000)
,但是保存了一个依赖。