0. JWS
서명
- 제3자에 의해 위조되지 않았음을 증명
- 생성자가 누군지 확인할 수 있음
Validation
- 헤더와 페이로드에 명시된 조건을 충족
- 옵션) 사용자 정의 조건도 추가 가능
1 Structure of a Signed JWT
예시) Header
더보기
{
"alg": "HS256", // 서명 알고리즘
"typ": "JWT" // 타입 (JWT 고정)
}
예시) Payload
더보기
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
예시) Signature
더보기
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
알고리즘
알고리즘 | 서명 방식 | 키 구조 | 특징 |
HS256 | HMAC + SHA-256 | 대칭 (하나의 비밀 키 공유) | 빠름, 단순, 키 유출 시 위조 가능 |
RS256 | RSA + SHA-256 | 비대칭 (개인 키/공개 키) | 보안성 높음, 공개 검증 가능 |
ES256 | ECDSA + SHA-256 | 비대칭 (개인 키/공개 키) | 경량, 보안성 높고 성능 우수 |
Algorithm Overview for Compact Serialization
- 각 부분별로 인코딩하여 합치는 구성을 가짐
코드) HMAC
더보기
const encodedHeader = base64(utf8(JSON.stringify(header)));
const encodedPayload = base64(utf8(JSON.stringify(payload)));
const signature = base64(
hmac(`${encodedHeader}.${encodedPayload}`, secret, sha256)
);
const jwt = `${encodedHeader}.${encodedPayload}.${signature}`;
- 공유 비밀키 사용
코드) RS256
더보기
const encodedHeader = base64(utf8(JSON.stringify(header)));
const encodedPayload = base64(utf8(JSON.stringify(payload)));
const signature = base64(
rsassa(`${encodedHeader}.${encodedPayload}`, privateKey, sha256)
);
const jwt = `${encodedHeader}.${encodedPayload}.${signature}`;
- 개인키로 서명
Practical Aspects of Signing Algorithms
구분 | 사용하는 키 | 누가 가능? | 구조 |
서명 | 개인키로 서명, 공개키로 검증 | 검증자: 다수 | Many-to-One |
암호화 | 공개키로 암호화, 개인키로 복호화 | 암호화자: 다수 | One-to-Many |
JWS Header Claims
클레임 | 설명 | 주요 용도 |
jku | 키 세트의 URL | 키 참조 (외부 URL) |
jwk | 공개 키 객체 | 키 직접 포함 |
kid | 키 식별자 | 다중 키 관리 |
x5u | X.509 인증서 URL | 외부 인증서 체인 참조 |
x5c | X.509 인증서 체인 | 인증서 직접 포함 |
x5t, x5t#S256 | 인증서 해시 | 인증서 무결성 확인 |
typ | 타입 지정 | "JWT", "JOSE" 등 |
crit | 필수 해석 항목 지정 | 구현체 확장 지원 |
JWS JSON Serialization
- JSON 형식의 구조화된 표현 방식을 사용하는 서명 방식 (Compact Serialization ❌)
- 하나의 JWT에 여러 개의 서명을 포함시킬 수 있음
- 즉, 하나의 payload에 대해 여러 주체가 각각 서명한 결과를 담을 수 있음
- 예시
- 여러 기관이 같은 데이터에 서명해야 할 때
- 다양한 알고리즘을 동시에 사용할 때
예) JWS JSON Serialization
더보기
{
"payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
"signatures": [
{
"protected": "eyJhbGciOiJSUzI1NiJ9",
"header": { "kid": "2010-12-29" },
"signature": "cP4hiU..."
},
{
"protected": "eyJhbGciOiJFUzI1NiJ9",
"header": { "kid": "e9bc097a..." },
"signature": "DtEhU3..."
}
]
}
키 | 설명 |
payload |
JWT의 본문 데이터, Base64URL로 인코딩됨
|
signatures | 여러 개의 서명을 담는 배열 |
signatures 필드 | 설명 |
protected |
서명 대상인 헤더 (Base64Url 인코딩된 JSON)
|
header | 서명되지 않는 헤더 (JSON 객체) |
signature | 실제 서명 값, Base64Url 인코딩 |
2. Signing and Validating Tokens
- JavaScript에서 널리 쓰이는 jsonwebtoken 라이브러리를 사용해서 서명된 JWT 생성
HS256: HMAC + SHA-256
예시) 서명
더보기
const secret = 'my-secret';
const signed = jwt.sign(payload, secret, {
algorithm: 'HS256',
expiresIn: '5s' // if ommited, the token will not expire
});
예시) 검증
더보기
const decoded = jwt.verify(signed, secret, {
// Never forget to make this explicit to prevent
// signature stripping attacks
algorithms: ['HS256'],
});
RS256: RSASSA + SHA256
bash) RSA 키 생성 (OpenSSL)
더보기
# 1. 개인키 생성 (2048비트)
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
# 2. 공개키 추출
openssl rsa -pubout -in private_key.pem -out public_key.pem
예시) 서명
더보기
import jwt from 'jsonwebtoken';
// 개인키 (private_key.pem의 내용 전체를 문자열로 붙여 넣음)
const privateRsaKey = `-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----`;
const payload = {
sub: "1234567890",
name: "John Doe",
admin: true
};
// JWT 서명 생성
const signed = jwt.sign(payload, privateRsaKey, {
algorithm: 'RS256',
expiresIn: '5s'
});
예시) 검증
더보기
// 공개키 (public_key.pem의 내용 전체)
const publicRsaKey = `-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----`;
// JWT 검증
const decoded = jwt.verify(signed, publicRsaKey, {
algorithms: ['RS256'] // 꼭 명시적으로 지정할 것!
});
ES256: ECDSA using P-256 and SHA-256
bash) ECDSA 키 생성 (OpenSSL)
더보기
# ECDSA 개인키 생성 (prime256v1 = P-256)
openssl ecparam -name prime256v1 -genkey -noout -out ecdsa_private_key.pem
# 공개키 추출
openssl ec -in ecdsa_private_key.pem -pubout -out ecdsa_public_key.pem
예시) 서명
더보기
import jwt from 'jsonwebtoken';
// 개인키 PEM 내용 전체 붙여넣기
const privateEcdsaKey = `-----BEGIN EC PRIVATE KEY-----
...
-----END EC PRIVATE KEY-----`;
const payload = {
sub: "1234567890",
name: "John Doe",
admin: true
};
const signed = jwt.sign(payload, privateEcdsaKey, {
algorithm: 'ES256',
expiresIn: '5s'
});
예시) 검증
더보기
// 공개키 PEM 내용 전체
const publicEcdsaKey = `-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----`;
const decoded = jwt.verify(signed, publicEcdsaKey, {
algorithms: ['ES256'] // 꼭 명시! (서명 제거 공격 방지)
});