以下是各签名算法的特点、摘要算法、依赖环境、密钥支持格式的表格:

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 格式

说明:

  1. ECDSA (ES 系列):公钥格式通常为 X.509 格式,私钥通常为 PKCS8 格式。
  2. HMAC (HS 系列):密钥是对称的,通常是一个字节数组或字符串,适合在对称密钥环境中使用。
  3. RSA (RS 系列):公钥通常为 X.509 格式,私钥通常为 PKCS#1 格式。
  4. 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);  
}