- Sign in with Apple 用クライアントシークレットをES256を用いたJWT形式で生成する必要があったため、Javaでその生成方法を検証した。その際のメモを残す。
JWTとは
-
JSON Web Tokenの略称。
-
電子署名による改竄検知機能を持ったトークン。
-
認証用途などで用いられる。
-
ピリオド(".")区切りのHeader部、Payload部、Signature部から構成される。
Header.Payload.Signature
-
Header部:JWTの検証に必要な情報から構成される。署名に使用するアルゴリズム(
alg
)や署名に使用する鍵(公開鍵/秘密鍵)を識別する値(kid
)などが含まれる。 -
Payload部:JWTに関する属性情報から構成される。JWTの発行者(
aud
)や発行日時(iat
)などの情報が含まれる。 -
Signature部:Header部とPayload部を合わせた文字列に署名した値。Header部に指定したアルゴリズムと秘密鍵を使用して署名を行う。
-
各部位はBase64URLエンコードされる。
-
署名アルゴリズムには、ES256やRSA-SHA256などが用いられる。
- ES256:「P-256 および SHA-256 を使⽤した ECDSA」の別称。
* ECDSA:Elliptic Curve Digital Signature Algorithm, 楕円曲線デジタル署名アルゴリズム
* P-256:使⽤されるアルゴリズムのバージョン。
- ES256:「P-256 および SHA-256 を使⽤した ECDSA」の別称。
サンプルコード
-
Sign in with Apple のJWT例を構成情報とした生成コード。
- デベロッパーコンソールから取得した鍵は使用せず、自前で生成した鍵を使用して署名を行う。
- エラーハンドリングなどは省略。
package jwt_test;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JWTTest {
private static ECPublicKey PUBLIC_KEY;
private static ECPrivateKey PRIVATE_KEY;
public static void main(String[] args) {
try {
String jwt = generateJWT();
verifyJWT(jwt, PUBLIC_KEY);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
// JWT生成
private static String generateJWT()
throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, SignatureException,
InvalidKeyException, JsonProcessingException {
// 署名鍵生成
// ※Sign in with Appleの場合は、コンソールから生成した秘密鍵を利用する。https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
keyPairGenerator.initialize(new ECGenParameterSpec("secp256r1"));
final KeyPair keyPair = keyPairGenerator.generateKeyPair();
PUBLIC_KEY = (ECPublicKey) keyPair.getPublic();
PRIVATE_KEY = (ECPrivateKey) keyPair.getPrivate();
byte[] publicKeyEncodedBytes = Base64.encodeBase64(PUBLIC_KEY.getEncoded());
byte[] privateKeyEncodedBytes = Base64.encodeBase64(PRIVATE_KEY.getEncoded());
System.out.println("ES256 Public Key:" + new String(publicKeyEncodedBytes));
System.out.println("ES256 Private Key:" + new String(privateKeyEncodedBytes));
// ヘッダー部設定
final ObjectMapper objectMapper = new ObjectMapper();
final Map<String, Object> jwtHeader = new LinkedHashMap();
jwtHeader.put("kid", "ABC123DEFG");
jwtHeader.put("alg", "ES256");
String jwtHeaderStr = Base64.encodeBase64URLSafeString(objectMapper.writeValueAsBytes(jwtHeader));
// ペイロード部設定
final Map<String, Object> jwtPayload = new LinkedHashMap();
jwtPayload.put("iss", "DEF123GHIJ");
jwtPayload.put("iat", 1437179036);
jwtPayload.put("exp", 1493298100);
jwtPayload.put("aud", "https://appleid.apple.com");
jwtPayload.put("sub", "com.mytest.app");
String jwtPayloadStr = Base64.encodeBase64URLSafeString(objectMapper.writeValueAsBytes(jwtPayload));
// 署名設定
final Signature jwtSignature = Signature.getInstance("SHA256withECDSAinP1363Format");
jwtSignature.initSign(PRIVATE_KEY);
jwtSignature.update((jwtHeaderStr + "." + jwtPayloadStr).getBytes());
byte[] jwtSignatureBytes = jwtSignature.sign();
final String jwtSignatureStr = Base64.encodeBase64URLSafeString(jwtSignatureBytes);
final String jwt = jwtHeaderStr + "." + jwtPayloadStr + "." + jwtSignatureStr;
System.out.println("jwt:" + jwt);
return jwt;
}
// 署名検証
public static void verifyJWT(String jwt, ECPublicKey publicKey) throws NoSuchAlgorithmException,
InvalidKeyException, SignatureException {
final String[] splitJwt = jwt.split("\\.");
final String jwtHeaderStr = splitJwt[0];
final String jwtPayloadStr = splitJwt[1];
final String jwtSignatureStr = splitJwt[2];
final Signature jwtSignature = Signature.getInstance("SHA256withECDSAinP1363Format");
jwtSignature.initVerify(publicKey);
jwtSignature.update((jwtHeaderStr + "." + jwtPayloadStr).getBytes());
if( jwtSignature.verify(Base64.decodeBase64(jwtSignatureStr))) {
System.out.println("Verifying Signature Success");
}else {
System.out.println("Verifying Signature Failure");
}
}
}
動作確認
ES256 Public Key:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaGzLv1quqEfO2YfyeMKJ5y72hVYP+NgH2tSqyKtW3MoxZShcY/p3aSw9af+N4bDwutbrTtLmEn4I6GbV71/bcQ==
ES256 Private Key:MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCBS+5M9cXf4BPwM8g8JTR4Dma26Etno8lAXg7EgsWht4A==
jwt:eyJraWQiOiJBQkMxMjNERUZHIiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJERUYxMjNHSElKIiwiaWF0IjoxNDM3MTc5MDM2LCJleHAiOjE0OTMyOTgxMDAsImF1ZCI6Imh0dHBzOi8vYXBwbGVpZC5hcHBsZS5jb20iLCJzdWIiOiJjb20ubXl0ZXN0LmFwcCJ9.WxE4w1JSf1dxcHJiDaFdRHstMveiwfqn0ZcwW_7PEJji0kRuJWCczt_AM3-RW6lgp8CDREfTnQTMRegnHohRKg
Verifying Signature Success
※生成されたjwtの中身をjwt.ioで確認した結果
-
Header部
{ "kid": "ABC123DEFG", "alg": "ES256" }
-
Payload部
{ "iss": "DEF123GHIJ", "iat": 1437179036, "exp": 1493298100, "aud": "https://appleid.apple.com", "sub": "com.mytest.app" }