以下是各签名算法的特点、摘要算法、依赖环境、密钥支持格式的表格:
KEY | 算法类型 | 摘要算法 | 依赖环境 | 特点 | 密钥支持格式 |
---|---|---|---|---|---|
ES384 | ECDSA(椭圆曲线签名算法) | SHA384 | openssl |
使用 P-384 曲线,签名安全性高,适合高性能与安全要求场景。 | 公钥:X.509 格式,私钥:PKCS8 格式 |
ES256 | ECDSA | SHA256 | openssl |
使用 P-256 曲线,签名较短,性能高效,适合对资源敏感的场景。 | 公钥:X.509 格式,私钥:PKCS8 格式 |
ES256K | ECDSA | SHA256 | openssl |
使用 secp256k1 曲线,常见于区块链应用,如比特币签名算法。 | 公钥:X.509 格式,私钥:PKCS8 格式 |
HS256 | HMAC(基于密钥的消息认证码) | SHA256 | hash_hmac (PHP自带) |
对称签名算法,简单高效,适合服务器内部使用,但密钥泄露风险高。 | 密钥:对称密钥(字节数组或字符串) |
HS384 | HMAC | SHA384 | hash_hmac (PHP自带) |
与 HS256 相似,但使用更长摘要算法,增强签名强度。 | 密钥:对称密钥(字节数组或字符串) |
HS512 | HMAC | SHA512 | hash_hmac (PHP自带) |
使用 SHA512,摘要更长,安全性更高,适合高安全性场景。 | 密钥:对称密钥(字节数组或字符串) |
RS256 | RSA 签名算法 | SHA256 | openssl |
使用非对称加密算法,公钥验证签名,适合分布式系统,安全性高。 | 公钥:X.509 格式,私钥:PKCS#1 格式 |
RS384 | RSA 签名算法 | SHA384 | openssl |
基于 RSA 和 SHA384,提供更强的签名安全性。 | 公钥:X.509 格式,私钥:PKCS#1 格式 |
RS512 | RSA 签名算法 | SHA512 | openssl |
使用 RSA 和 SHA512,摘要更长,适合需要更高签名强度的场景。 | 公钥:X.509 格式,私钥:PKCS#1 格式 |
EdDSA | Ed25519/Ed448 非对称签名算法 | EdDSA | sodium_crypto |
使用现代椭圆曲线 Ed25519,计算高效,签名短小,适合对性能要求极高的场景。 | 公钥:Ed25519 格式,私钥:Ed25519 格式 |
说明:
- ECDSA (ES 系列):公钥格式通常为 X.509 格式,私钥通常为 PKCS8 格式。
- HMAC (HS 系列):密钥是对称的,通常是一个字节数组或字符串,适合在对称密钥环境中使用。
- RSA (RS 系列):公钥通常为 X.509 格式,私钥通常为 PKCS#1 格式。
- EdDSA:公钥和私钥都使用 Ed25519 格式,专为现代加密算法设计,广泛用于高效的签名验证。
以RS256验证 Apple login token
$jwt = 'eyJraWQiOiJUOHRJSjF6U3JPIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmNpaGFpNTIwLm14c2QiLCJleHAiOjE3MzM1NDMyMDIsImlhdCI6MTczMzQ1NjgwMiwic3ViIjoiMDAxNDQzLjUyNWRiNWFmNjZiYjQ5ZThhMjNkNjZiN2Q5YWE3MmUyLjAzNDYiLCJjX2hhc2giOiJ1TThDYUlSWnlQSjVpVHFIaVpCM25BIiwiZW1haWwiOiIzMTYyNTExMzI0QHFxLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdXRoX3RpbWUiOjE3MzM0NTY4MDIsIm5vbmNlX3N1cHBvcnRlZCI6dHJ1ZSwicmVhbF91c2VyX3N0YXR1cyI6Mn0.j0tj2vwp2faXLyLajMVR3MxTbTni-NbpHJFNfqqdEeS8L9UvEP2gYhN9BA8TzeNVoY6gqtLhhBlpg4bvQUMWTy0hm_VQjLxsbkbu6FbwioRElGktDh38dWI8T5nVKWmmfDuIJs2l8FtqFuASfKhNyIPyZhxeyzr-NnPZ63U53w2OGg4x3F2GJCTlrW7PBwuspDZ9tX761eDPwfnURgqTpgEf4UrwRJuu0s7Gq-yC4G4sPzEazgqrxs0w2bvLVLRt981bl6N2NeGCC9JRTYMcQvokWNkGAHn8mU1B89GuNGhr6zHkkoZSZibs54CKdX8TV_UOOZf4fRio-dHVVeV3_A';
// 1. 解析 JWT
list($headb64, $bodyb64, $cryptob64) = explode('.', $jwt);
// 解码 base64url 编码的头部
$decoded_header = json_decode(urlsafeB64Decode($headb64), true);
if (!$decoded_header || !isset($decoded_header['kid'])) {
echo "Invalid JWT header.";
exit;}
$kid = $decoded_header['kid']; // 获取 JWT 中的 kid
// 获取 Apple 的公钥集合
$apple_jwks_url = 'https://appleid.apple.com/auth/keys';
$apple_jwks = json_decode(file_get_contents($apple_jwks_url), true);
// 找到与 kid 匹配的公钥
$pem = null;
foreach ($apple_jwks['keys'] as $key) {
if ($key['kid'] === $kid) {
// 将 JWK 转换为 PEM 格式
//$publicKey = convertJWKToPEM($key);
$pem = convertJWKToPEM($key['n'],$key['e']);
break; }
}
$publicKey = \openssl_pkey_get_public($pem);
if (!$publicKey) {
echo "Public key not found for the given kid.";
exit;
}
//$publicKey = <<<EOT
//-----BEGIN PUBLIC KEY-----
//MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAteUbLrwScsjVrcFAvSrf
//ben3eQaEca3ESBegGh/wdGuLKw6QgwDxY3fC1/WeSVnkJXx72ddw3j2inoADnTyz
//uNa/PwDSmvJhOhmzOmoltmtKHteGdaXrqMohO6A85WxVKbN7pzDqwZJNrdY12LOl
//tlI8PHIG+elAbKM2XOHiJaZnLpAVckKy6MQYsEExpPB3plGxWZElqwNZY6SUDVeN
//+o9qg5FJOFg7T7iTVVEagws4DM6uZNMDQGtqg9V9VqPQkUzC+sYd5eqbB9LqH4iN
//5F6OB7BmD3g3jCu9zgh3O9V24N43EruBCNrmP0xLP5ZliKqozoAcd1nv71HuVm6m
//gQIDAQAB
//-----END PUBLIC KEY-----
//EOT;
// 提取地址:https://8gwifi.org/jwkconvertfunctions.jsp
$sig = urlsafeB64Decode($cryptob64);
$success = \openssl_verify("{$headb64}.{$bodyb64}", $sig, $publicKey, 'SHA256');
if ($success === 1) {
return true;
}
if ($success === 0) {
return false;
}
// returns 1 on success, 0 on failure, -1 on error.
throw new DomainException(
'OpenSSL error: ' . \openssl_error_string()
);
function convertJWKToPEM($n, $e) {
$mod = urlsafeB64Decode($n);
$exp = urlsafeB64Decode($e);
$modulus = \pack('Ca*a*', 2, encodeLength(\strlen($mod)), $mod);
$publicExponent = \pack('Ca*a*', 2, encodeLength(\strlen($exp)), $exp);
$rsaPublicKey = \pack(
'Ca*a*a*',
48,
encodeLength(\strlen($modulus) + \strlen($publicExponent)),
$modulus,
$publicExponent
);
// sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption.
$rsaOID = \pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA
$rsaPublicKey = \chr(0) . $rsaPublicKey;
$rsaPublicKey = \chr(3) . encodeLength(\strlen($rsaPublicKey)) . $rsaPublicKey;
$rsaPublicKey = \pack(
'Ca*a*',
48,
encodeLength(\strlen($rsaOID . $rsaPublicKey)),
$rsaOID . $rsaPublicKey
);
return "-----BEGIN PUBLIC KEY-----\r\n" .
\chunk_split(\base64_encode($rsaPublicKey), 64) .
'-----END PUBLIC KEY-----';
}
function urlsafeB64Decode($input)
{
return \base64_decode(convertBase64UrlToBase64($input));
}
function convertBase64UrlToBase64($input)
{
$remainder = \strlen($input) % 4;
if ($remainder) {
$padlen = 4 - $remainder;
$input .= \str_repeat('=', $padlen);
}
return \strtr($input, '-_', '+/');
}
function encodeLength($length)
{
if ($length <= 0x7F) {
return \chr($length);
}
$temp = \ltrim(\pack('N', $length), \chr(0));
return \pack('Ca*', 0x80 | \strlen($temp), $temp);
}