关于 JWT、JWS、JWE

JWT(JSON Web Token)

JWT 是一个字符串,表示了一组字段声明的集合,以 JSON 格式组织数据,并以 JWS 或 JWE 方式编码。

JWT 由 Header、Payload、Signature 三部分组成,三个部分之间使用英文 . 分隔。

JWTString = Base64(Header) + "." + Base64(Payload) + "." + Signature

Header

JWT标头,是一个描述JWT元数据的JSON对象。

字段名

类型

说明

alg

String

签名使用的算法。如:HMAC SHA256缩写为 HS256

typ

String

声明 JWT 的媒体类型(IANA.MediaTypes)。一般固定赋值为JWT

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload

JWT荷载,也是一个JSON对象,包含需要传输的数据。荷载字段有三种声明方式:

    • Registered Claims
    • Public Claims
    • Prigate Claims

Registered Claims

RFC7519中规定的JWT注册声明字段,并非强制要求JWT有这些字段,但它提供了一些JWT共有的字段。注册声明字段名非常短,因为JWT的特征之一是紧凑的数据格式。

字段名

类型

说明

iss

发行人,可填充应用标识

exp

到期时间,是一个时间戳

sub

主题,JWT面向的用户

aud

用户,JWT的接收方

nbf

在此时间前JWT不可用

iat

发布时间,时间戳

jti

JWT ID,即JWT的标识ID

Public Claims

由使用JWT的组织定义,为了防止命名冲突,需要向 IANA JSON Web Token Registry 注册字段定义,并符合其命名规则。

Private Claims

私有声明是由JWT的发布方和接收方约定的字段,非公开声明的字段。

Signature

JWT 签名。对 Header 和 Payload 的 Base64编码值 使用加密算法进行计算后,得到签名。计算签名时用到的密钥(secret)被称为 JWK(JSON Web Key)

Signature 部分是可选的。如果一个 JWT 无 Signature 部分,则被称为“Unsecured JWT”。无 Signature 部分时,标头的alg字段应声明值为none

以 HMAC SHA256 加密算法为例,签名的计算公式为:

Signature = HMACSHA256(Base64(header) + "." + Base64(payload), secret)

示例

一个 Unsecured JWT 示例如下:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2F1dGguYnJvemVuLnRvcCIsImF1ZCI6IldlQ2hhdEFwcCIsInN1YiI6IjEyMzEyMyIsImlhdCI6MTY1NTg5MzYzMiwibmJmIjoxNjU1ODkzNjMyLCJqdGkiOiJ0ZXN0U2Vzc2lvbklkIiwiZ3JvdXBzIjpbIldlQ2hhdEFwcCJdLCJub25jZSI6InRlc3RTZXNzaW9uSWQiLCJleHAiOjE2NTU5ODAwMzJ9

Header 部分解码后为(已格式化):

{
	"alg": "HS256",
	"typ": "JWT"
}

这里为了演示,直接使用了 JWS 移除了 Signature 部分,因此 Header 部分带有签名算法 alg 字段。

Payload 部分解码为(已格式化):

{
	"iss": "https://auth.brozen.top",
	"aud": "WeChatApp",
	"sub": "123123",
	"iat": 1655893632,
	"nbf": 1655893632,
	"jti": "testSessionId",
	"groups": ["WeChatApp"],
	"nonce": "testSessionId",
	"exp": 1655980032
}

JWS(JSON Web Signature)

JWS 是在 Unsecured JWT 基础上,Header 部分声明签名算法,并添加 Signature 部分。

JWS 通过添加签名,让接收方能够校验 JWT 有效性,签名由 Payload 经过加密算法计算得到。如接收方使用通用的算法、密钥对 Header 和 Payload 加密,得到的密文与 Signature 部分不同,说明 Payload 被篡改。

JWS 的结构与包含了 Signature 部分的 JWT 相同。

Header

JWS 的标头除了 JWT 的标头algtyp之外,还有一些扩展标头:

字段名

类型

说明

alg

String

签名使用的算法,如:HMAC SHA256缩写为 HS256。

RFC7518 中规定的合法加密算法参考此处

typ

String

令牌类型,统一固定为"JWT"。

jku

String

JWK Set URL,是一个URI,用于获取 JWK 集合。此 URL 需要在传输层提供安全保护(TLS、HTTPS)。此标头非必填。

jwk

String

是一个 JWK,是用于签名此 JWS 的加密公钥。此标头非必填。

kid

String

Key ID,是一个大小写敏感字符串,JWK 的发布者给接受者的提示信息,用于表明加密此 JWS 使用的 Key。此标头非必填。可配合 jwk 标头使用。

x5u

String

是一个URI,指向加密此 JWS 的 X.509数字证书或证书链。此标头非必填。

x5c

String

是加密此 JWS 的 X.509数字证书或证书链。此标头非必填。

x5t

String

是加密此 JWS 的 X.509数字证书的 SHA-1 指纹,Base64格式。此标头非必填。

x5t#S256

String

是加密此 JWS 的 X.509数字证书的 SHA-256 指纹,Base64格式。此标头非必填。

cty

String

是此 JWS 的荷载部分的媒体类型,可能不会被使用。此标头非必填。

crit

String[]

是一组 JWS 标头名,接受此 JWS 的应用必须理解并能够处理这组标头,如存在无法处理的标头,应用应当认为此 JWS 是非法的。此列表中的标头不可以是上述的标准头(可以是公有头或私有头)。此标头非必填。

验签

使用 JWS 的目的是为了保护 JWT 不被篡改,因此 JWT 的接收方需要验证 Signature 的有效性,此过程称为验签。

非对称加密算法加密的 Signature,发布方使用私钥进行签名,接收方可以使用公钥校验 Header 与 Payload(解密 Signature 校验)。

对称加密算法要求接收方拥有与 JWS 发布方相同的密钥,接收方使用相同的密钥校验 Header 与 Payload(加密 Payload 校验)。

示例

一个合法 JWS 的示例如下:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2F1dGguYnJvemVuLnRvcCIsImF1ZCI6IldlQ2hhdEFwcCIsInN1YiI6IjEyMzEyMyIsImlhdCI6MTY1NTg5MzYzMiwibmJmIjoxNjU1ODkzNjMyLCJqdGkiOiJ0ZXN0U2Vzc2lvbklkIiwiZ3JvdXBzIjpbIldlQ2hhdEFwcCJdLCJub25jZSI6InRlc3RTZXNzaW9uSWQiLCJleHAiOjE2NTU5ODAwMzJ9.t5Gqvyz6GNHsVPlnJ3Oq7vNH0T3E7MNvTj_aDcgI9lE

Header 与 Payload 部分和 JWT 示例相同,签名使用 HS256 算法,密钥为:secretddsecretddsecretddsecretdd

JWE(JSON Web Encryption)

JWS 中,JWT Payload 部分仍是明文,第三方获取到之后可以直接查看。JWE 的目的是对 JWT 整体进行加密,防止第三方截获解析 JWT。

JWE 的目的是在 JWT 发布方加密 JWT、在 接收方解密 JWT,因此 JWE 必须采用对称加密算法。同时由于对称加密的存在,JWE 的接收方可以在 JWT 中新增 Claims,这是 JWS 不支持的。

JWE 由 Header、加密密钥、加密算法的IV、密文、算法附加数据组成,格式如下:

JWEString = Base64(Header) + "." + Base64(Encrypted Key) + "." + Base64(Initialization Vector) + "." + Base64(Encrypted Data) + "." + Base64(Auth Tag)

JWE 结构

Header

JWE 的 Header 与 JWS 大部分相同,仅有如下区别,其他头部 jku / jkw / kid / x5u / x5c / x5t / x5t#S256 / typ / cty / crit 与 JWS 定义完全一致。

字段名

类型

说明

typ

String

令牌类型,统一固定为"JWT"。

alg

String

加密密钥使用的算法,改算法用于对“加密数据的密钥”进行加密。

RFC7518 中规定的合法加密算法有:

  • RSA1_5:RSAES-PKCS1-v1_5
  • RSA-OAEP:RSAES OAEP using SHA-256 and MGF1 with SHA-256
  • A128KW
  • A192KW
  • A256KW
  • ECDH-ES
  • ECDH-ES+A128KW
  • ECDH-ES+A192KW
  • ECDH-ES+A256KW
  • A128GCMKW
  • A192GCMKW
  • A256GCMKW
  • PBES2-HS256+A128KW
  • PBES2-HS384+A192KW
  • PBES2-HS512+A256KW

enc

String

加密数据的算法名称。RFC7518 中规定的合法加密算法有:

  • A128GCM:AES GCM using 128-bit key
  • A192GCM:AES GCM using 192-bit key
  • A256GCM:AES GCM using 256-bit key
  • A128CBC-HS256:AES_128_CBC_HMAC_SHA_256
  • A192CBC-HS384:AES_192_CBC_HMAC_SHA_384
  • A256CBC-HS512:AES_256_CBC_HMAC_SHA_512

zip

String

加密前的数据压缩算法,可以不填。

Encrypted Key

这里是对数据加密的密钥,经过 alg 算法处理后的密文。也即一个 JWE 中至少存在两种加密算法,分别用于:对数据加密、对加密数据的密钥加密。

对加密数据的密钥进行加密后,需要使用 JWK 密钥管理模式来导出密钥。

Initialization Vector

部分加密算法需要额外数据,或随机数据,在此字段中存储。

Encrypted Data

数据被加密后的密文。

Authentication Tag

认证标记,数据加密算法产生的附加数据,用于防止密文被篡改。

为什么要先使用密钥加密数据,再对密钥进行加密?

由于数据比较大,使用带有公私钥密钥对的对称加密算法(如 RSA)加密会很慢,因此往往采用 AES-GCM(Galois/Counter Model) 或 AES_CBC_HMAC_SHA 算法,此种算法只有一个加密密钥,但加解密速度快。因此使用 AES 单密钥算法对数据加密,然后使用 RSA 对 AES 密钥加密,既保证了安全性(数据被加密、AES密钥不会泄漏),又保障了加密速度(AES 加密数据快,RSA 仅加密密钥也比较快)。

JWK(Json Web Key)

JWK 是一个 JSON 格式的对象,表示加密用的密钥。JWK 中的字段表示密钥的相关属性。

JWKs 是一个 JSON 格式的对象,有一个 JWK 数组字段,数组中每个元素的字段与 JWK 相同,表示 JWK 的集合。

JWK 格式

字段名

类型

说明

kty

String

密钥生成时使用的加密算法,大小写敏感。取值定义与 JWA,如“RSA”、“EC”。必填项。

use

String

公钥的使用目的,用于加密数据或验证数据签名。取值有:

  • sig 签名验证
  • enc 加密数据

必填项。

key_ops

String[]

表示使用此密钥的操作,取值有:

  • sign 计算签名
  • verify 验证签名
  • encrypt 加密数据
  • decrypt 解密数据与验证
  • warpKey 加密密钥
  • unwarpKey 解密密钥与验证
  • deriveKey 产生密钥
  • deriveBits 产生bits,此bits不用于密钥

必填项。

alg

String

使用此密钥的加密算法。如“RS256”、“ES256”。必填项。

kid

String

密钥ID,用于在 JWK 集合中匹配单个密钥。

n

公钥模值。

e

公钥指数。

x5u

String

是一个URI,指向加密此 JWS 的 X.509数字证书或证书链。

x5c

String

是加密此 JWS 的 X.509数字证书或证书链。

x5t

String

是加密此 JWS 的 X.509数字证书的 SHA-1 指纹,Base64格式。

x5t#S256

String

是加密此 JWS 的 X.509数字证书的 SHA-256 指纹,Base64格式。此标头非必填。

例如,一个 kid 为 "sign-key" 的 HS256 算法 JWK 文件格式如下:

{
	"kty": "oct",
	"kid": "sign-key",
	"use": "sig",
	"key_ops": ["sign", "verify"],
	"alg": "HS256",
	"k": "RqVfW84rHBqwabFnZaRh_td19WvQT0x1b1u_MxsEyRs"
}

JWK Set 格式

字段名

类型

说明

keys

JWK[]

JWK 密钥。

例如,包含两个 JWK 的 JWK Set 文件格式如下,包含两个 HS256 算法密钥, kid 为 "sing-key" 和 "content-enc-key"。

{
	"keys": [{
			"kty": "oct",
			"kid": "sign-key",
			"use": "sig",
			"key_ops": ["sign", "verify"],
			"alg": "HS256",
			"k": "RqVfW84rHBqwabFnZaRh_td19WvQT0x1b1u_MxsEyRs"
		},
		{
			"kty": "oct",
			"kid": "content-enc-key",
			"use": "sig",
			"key_ops": ["sign", "verify"],
			"alg": "HS256",
			"k": "PwVsCTlHKSwmZWYF2yXXSBc0mkuqecyOXY5o7M9iKR0"
		}
	]
}

JWK 生成

以 jose4j 库为例,介绍 JWK 如何生成。下面以 HMAC、RSA 两种常见算法为例介绍。

HMAC

OctetSequenceJsonWebKey key = OctJwkGenerator.generateJwk(256);
key.setKeyId("sign-key");
key.setAlgorithm("HS256");
key.setUse("sig");
key.setKeyOps(List.of("sign", "verify"));
key.setOtherParameter("kty", "oct");
System.out.println(key.toJson());

RSA

这里使用的是 RSA-OAEP-256 算法。注意 Java 中只能使用 PKCS#8 格式私钥,如果从 PEM 文件中读取到 PKCS#1 格式,需要使用加密库完成私钥格式转换,例如 Bouncy Castle。

public JsonWebKey createRSAKeys(String keyId) {
    try {
        // 生成私钥,或者从文件读取私钥皆可。
        KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance("RSA");
        kpGenerator.initialize(2048);
        KeyPair kp = kpGenerator.generateKeyPair();
        PrivateKey prvKey = kp.getPrivate();

        // 生成 JWK
        RsaJsonWebKey key = RsaJwkGenerator.generateJwk(2048);
        key.setKeyId(keyId);
        key.setAlgorithm("RSA-OAEP-256");
        key.setUse("enc");
        key.setKeyOps(List.of("encrypt", "decrypt", "warpKey", "unwarpKey", "encryption"));
        key.setOtherParameter("kty", "RSA");
        key.setPrivateKey(prvKey);
        return key;
    } catch (Exception e) {
        log.error("生成 RSA JWK 失败", e);
        throw new IllegalStateException("生成 RSA JWK 失败", e);
    }
}

JWA(Json Web Algorithm)

JWA 是 RFC7581 中规定的算法,用于 JWT 的签名或加密。

JWS 签名算法

"alg" 标头取值

算法描述

HS256

HMAC using SHA-256

HS384

HMAC using SHA-384

HS512

HMAC using SHA-512

RS256

RSASSA-PKCS1-v1_5 using SHA-256

RS384

RSASSA-PKCS1-v1_5 using SHA-384

RS512

RSASSA-PKCS1-v1_5 using SHA-512

ES256

ECDSA using P-256 and SHA-256

ES384

ECDSA using P-384 and SHA-384

ES512

ECDSA using P-512 and SHA-512

PS256

RSASSA-PSS using SHA-256 and MGF1 with SHA-256

PS384

RSASSA-PSS using SHA-384 and MGF1 with SHA-384

PS512

RSASSA-PSS using SHA-512 and MGF1 with SHA-512

JWE Key 加密算法

"alg" 标头取值

算法描述

附加 Header

RSA1_5

RSAES-PKCS1-v1_5

RSA-OAEP

RSAES OAEP

RSA-OAEP-256

RSAES OAEP using SHA-256 and MGF1 with SHA-256

A128KW

AES Key Wrap with value using 128-bit key

A192KW

AES Key Wrap with value using 192-bit key

A256KW

AES Key Wrap with value using 256-bit key

ECDH-ES

Elliptic Curve Diffie-Hellman Ephemeral Static key agreement using Concat KDF

  • epk
  • apu
  • apv

ECDH-ES+A128KW

ECDH-ES using Concat KDF and CEK wrapped with A128KW

  • epk
  • apu
  • apv

ECDH-ES+A192KW

ECDH-ES using Concat KDF and CEK wrapped with A192KW

  • epk
  • apu
  • apv

ECDH-ES+A256KW

ECDH-ES using Concat KDF and CEK wrapped with A256KW

  • epk
  • apu
  • apv

A128GCMKW

Key wrapping with AES GCM using 128-bit key

  • iv
  • tag

A192GCMKW

Key wrapping with AES GCM using 192-bit key

  • iv
  • tag

A256GCMKW

Key wrapping with AES GCM using 256-bit key

  • iv
  • tag

PBES2-HS256+A128KW

PBES2 with HMAC SHA-256 and "A128KW" wrapping

  • p2s
  • p2c

PBES2-HS384+A192KW

PBES2 with HMAC SHA-384 and "A192KW" wrapping

  • p2s
  • p2c

PBES2-HS512+A256KW

PBES2 with HMAC SHA-512 and "A256KW" wrapping

  • p2s
  • p2c

JWE Content 加密算法

"enc" 标头取值

算法描述

A128CBC-HS256

AES_128_CBC_HMAC_SHA_256 authenticated encryption algorithm

A192CBC-HS384

AES_192_CBC_HMAC_SHA_384 authenticated encryption algorithm

A256CBC-HS512

AES_256_CBC_HMAC_SHA_512 authenticated encryption algorithm

A128GCM

AES GCM using 128-bit key

A192GCM

AES GCM using 192-bit key

A256GCM

AES GCM using 256-bit key

常见加密算法与分类

非对称加密

非对称加密算法加密数据和解密数据使用不同的密钥,分为公钥、私钥。数据经过公钥加密后,可以使用私钥校验或解密。

一般来说数据接受方会生成一对公钥、私钥,然后将公钥公开。数据发布方使用公开的公钥加密数据,接收方使用私钥 解密/校验 数据。

在传输过程中,如果是加解密算法,即时数据被截获,第三方没有私钥也无法完成数据解密;如果是签名算法,即时修改了数据,也无法修改签名,会被校验出数据异常。

非对称加密的缺点:

  1. 加密速度慢;

非对称加密的优点:

  1. 数据传输安全,可以检测出数据是否被篡改。

RSA

RSA 算法可以进行数据加密、解密。

RSA 算法安全强度与密钥位数有关,位数越长则越难破解,同时加密也更耗时。主流长度有 1024位、2048位、4096位,目前低于1024位的密钥已经能被暴力破解,推荐使用 2048位密钥长度。

RS256 是 RSA-SHA256 算法的简写。可以使用 OpenSSL 生成 RSA 公私钥。

# 1. 生成 2048 位的 RSA 密钥,默认PKCS#1格式
openssl genrsa -out rsa-private-key.pem 2048

# 1.1 将 PKCS#1 格式私钥转为 PKCS#8
openssl pkcs8 -topk8 -inform PEM -in rsa-private-key.pem -outform PEM -nocrypt -out rsa-private-key-pkcs8.pem

# 1.2 PKCS#8 私钥转为 PKCS#1 格式
openssl rsa -pubin -in rsa-private-key-pkcs8.pem -RSAPublicKey_out -out rsa-private-key.pem


# 2. 通过密钥生成公钥
openssl rsa -in rsa-private-key.pem -pubout -out rsa-public-key.pem

ECDSA

ECDSA 算法密钥可以使用 OpenSSL 生成。

# 1. 生成 ec 算法的私钥,使用 prime256v1 算法,密钥长度 256 位。(强度大于 2048 位的 RSA 密钥)
openssl ecparam -genkey -name prime256v1 -out ecc-private-key.pem

# 2. 通过密钥生成公钥
openssl ec -in ecc-private-key.pem -pubout -out ecc-public-key.pem

对称加密

对称加密算法在加密和解密过程使用同一个密钥。

对称加密的优点:

  1. 计算量小,加解密速度快;

对称加密的缺点:

  1. 密钥单一,需妥善保管;

AES

AES 是常见的对称加密算法,密钥长度一般为 128位、192位、256位。更多细节请参考:AES算法详解_三文鱼先生的博客-CSDN博客_aes算法

签名算法

个人理解签名算法即信息摘要算法,通过数据原文与密钥计算得到信息摘要(签名),信息摘要无法反推得到原文。因此信息摘要可用于验证原文是否被修改。

HMAC

HMAC 信息摘要算法分为 MD 和 SHA 两大类,常见算法如:MD5、SHA256、SHA384、SHA512。

注意 HS256 算法要求的密钥要有 256 位,在 Java 中单个字符占8位,因此密钥文本(如“secret-text-sample”字符串)至少要有32个字符。

RFC 7519 - JSON Web Token (JWT)

RFC 7515 - JSON Web Signature (JWS)

RFC 7516 - JSON Web Encryption (JWE)

深入了解Json Web Token之概念篇 - FreeBuf网络安全行业门户

How to generate a JSON Web Key (JWK) | Connect2id

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值