【问题标题】:Signing JWT - do I do it wrong?签署 JWT - 我做错了吗?
【发布时间】:2019-12-15 14:26:03
【问题描述】:

出于教育目的,我正在尝试用 JavaScript 制作 JWT 生成器。有一个 jwt.io 工具可以创建和/或验证 JWT。

我正在努力让我的结果与验证器的结果相匹配。问题是签名。

这是我的代码:

function base64url(input) {
    return btoa(typeof input === 'string' ? input : JSON.stringify(input))
        .replace(/=+$/, '')
        .replace(/\+/g, '-')
        .replace(/\//g, '_');
}

const JWT = {
    encode(header, payload, secret) {
        const unsigned = [base64url(header), base64url(payload)].join('.');

        return [unsigned, base64url(sha256.hmac(secret, unsigned))].join('.');
    }
};

为了加密 HMAC SHA256,我使用了 js-sha256 库和 sha256.hmac(key, value) 原型。我将它与在线工具进行了比较,效果很好。

现在,我使用以下代码对其进行测试:

const jwt = JWT.encode(
    {
        alg: 'HS256',
        typ: 'JWT'
    },
    123,
    'xxx'
);

我得到的结果是:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.MTIz.NzhlNTFmYzUxOGQ2YjNlZDFiOTM0ZGRhOTUwNDFmMzEwMzdlNmZkZWRhNGFlMjdlNDU3ZTZhNWRhYjQ1YzFiMQ

另一方面,jwt.io 的结果是:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.MTIz.eOUfxRjWs-0bk03alQQfMQN-b97aSuJ-RX5qXatFwbE

如您所见,在我的结果和jwt.io 结果中,三分之二的 JWT 块是相同的。签名不同,如果你问我,它生成的签名非常短。该工具还将我自己的 JWT 标记为无效。

我检查了在线 HMAC SHA256 生成器,看起来我的代码创建了一个有效的签名,所以:

base64url(sha256.hmac('xxx', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.MTIz')) ===
'NzhlNTFmYzUxOGQ2YjNlZDFiOTM0ZGRhOTUwNDFmMzEwMzdlNmZkZWRhNGFlMjdlNDU3ZTZhNWRhYjQ1YzFiMQ'

jwt.io 是刚刚坏掉还是以其他方式造成的?

【问题讨论】:

  • 我也想知道。它只发生在对称类型

标签: javascript jwt


【解决方案1】:

我不会说你做错了,但你错过了一个小而重要的细节。 jwt.io 的结果是正确的,您计算的 hash 也是正确的。但是您使用哈希创建的签名不正确。

您使用sha256.hmac(secret, unsigned) 计算的哈希是一个大数,但函数的返回值是该大数的十六进制字符串表示。对于签名,您需要对原始数字进行 base64url 编码,而不是字符串表示形式。

我修改了您的代码,以便将哈希值直接编码为 base64url(node.js 版本):

const JWT = {
    encode(header, payload, secret) {
        const unsigned = [base64url(header), base64url(payload)].join('.');
        const hash  = sha256.hmac(secret, unsigned);
        console.log(hash);        
        var signature = new Buffer.from(hash, 'hex').toString('base64').replace(/\+/g,'-').replace(/\=+$/m,'');

        return [unsigned, signature].join('.');
    }
};

或者,如果你不使用 node.js,你可以使用它来代替(如 Robo Robok 所建议的):

const JWT = {
    encode(header, payload, secret) {
        const unsigned = [base64url(header), base64url(payload)].join('.');

        return [unsigned, base64url(sha256.hmac(secret, unsigned).replace(/\w{2}/g, byte => String.fromCharCode(parseInt(byte, 16))))].join('.');
    }
};

结果是一个令牌,与使用jwt.io创建的令牌相同:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.MTIz.eOUfxRjWs-0bk03alQQfMQN-b97aSuJ-RX5qXatFwbE

另请参阅my answer here,我在其中解释了比较不同工具的结果的步骤。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多