5G 보안 관련 NF 개발 중에 JOSE(JSON Object Signing and Encryption) 규격을 분석했었는데 이번 포스팅에 정리해봅니다.
개요
JOSE는 당사자간에 claim을 안전하게 전송하기 위한 프레임워크
- claim : key/value 쌍의 client 메타정보 set
- 예) 사용자의 권한 단계, 권한 스코프 => {"level": "root", "scope": "admin"}
JOSE 프레임워크는 이를 위한 몇 가지 규격들을 제공
- JWT(JSON Web Token)
- JWS(JSON Web Signature)
- JWE(JSON Web Encryption)
- JWK(JSON Web Keys)
규격에 따르면 JWT는 권한 claim 집합을 JWS 혹은 JWS와 JWE 구조로 인코드 한 JSON 객체로 표현
- JWT = 서명되지 않은 claim 집합 ------기술적인 호칭
- JWT = JWS ------------------------------일반적인 호칭
- JWT = JWE
- JWT = JWS + JWE --------------------일반적인 호칭
JWS와 JWE
- JWS는 단순히 claim set에 서명을 포함하여 Base64로 인코딩 되어 있음 -> 위변조 되지는 않으나 누구나 claim을 읽을 수 있음(무결성 O, 기밀성 X)
- JWE는 단순히 claim set을 암호화 -> 누구나 읽을 순 없으나 서명이 없어 인증 및 권한 부여로 쓸 순 없음(무결성 X(O), 기밀성 O)
- JWE는 Authenticated Encryption with Associated Data (AEAD) 알고리즘을 사용하여 Integrity도 보장 가능
- 대칭키를 이용해 내용 Encrypt => CEK(Content Encryption Key)
- 비대칭키를 이용해 CEK를 Encrypt
- 이 방식은 Client에게 대칭키를 숨길수 없음, 신뢰할수없는 Client와 연동하는 경우 추천되지 않음
- JWS + JWE = 보안성(무결성 O, 기밀성 O)
- 즉, 둘이 상호 보완하여 안전한 claim 전송이 가능
JWS와 JWE를 같이 사용하는 방법
- JWS의 결과를 JWE의 Payload(plain text) 부분에 넣는 Nested JWS 방식을 사용(일반적)
- JWE의 결과를 JWS의 Payload 부분에 넣는 Nested JWE 방식을 사용
JWT Structure
구조
- 마침표(.)로 구분되어 세 부분으로 나뉨
- Header
- Payload
- Signature
예 : hhhhh.ppppp.sssss
Header
- alg : signing 알고리즘
- typ : token의 타입 (JWT)
- 아래와 같이 구성
- 위 구조가 그대로 base64로 인코딩 됨
Payload
- claim들로 구성
- claim은 entity(일반적으로 user)의 상태와 추가적인 데이터
- 3가지 타입의 claim 존재(registered, public, private)
- registered claims
- 필수는 아니지만 유용하고 상호 호환되는 claim set을 제공하기 위해 추천되는 사전 정의된 claim set이 있음
- iss(issuer)
- exp(expiration time)
- sub(subject)
- aud(audience)
- 등등(https://tools.ietf.org/html/rfc7519#section-4.1)
- 필수는 아니지만 유용하고 상호 호환되는 claim set을 제공하기 위해 추천되는 사전 정의된 claim set이 있음
- public claims
- JWT를 사용하는 사람들이 자유롭게 정의 가능
- 충돌을 피하기 위해 https://www.iana.org/assignments/jwt/jwt.xhtml 에서 정의되거나 충돌 방지를 위한 namespace가 포함된 URI로 정의되어야 함
- private claims
- 두 당사자 간의 동의 하에 registered, public이 아닌 정보를 공유하기 위한 custom claim
- registered claims
- 아래와 같이 구성
- 위 구조가 base64로 인코딩 됨
Signature
- 서명 부분을 만들려면 인코딩 된 header, 인코딩 된 payload, secret을 header에 저장된 algorithm으로 서명해야 함
- 예로 만약 HMAC SHA 256 알고리즘을 사용하면 방식은 아래와 같다.
- 서명은 메시지가 변경되지 않았음을 확인하는 데 사용되며 개인키로 서명된 토큰의 경우 JWT 발신자가 자신이 말하는 사람인지 확인 가능
결과
- 세 개의 Base64-URL이 dot로 구분된 문자열로 출력
- 이는 HTML, HTTP 환경에서 쉽게 전송 가능
- SAML 같은 XML 기반에 비해 훨씬 더 간편
- 위의 예에서 인코딩 된 header, payload와 secret으로 서명된 예는 아래와 같음
쉽게 사용하기 위해서 jwt.io의 debugger를 사용해볼 수 있음(http://jwt.io/)
실제 사용
과정
- 사용자가 credential을 사용하여 성공적으로 로그인하면 JWT가 반환됨
- JWT는 사용자의 local storage 혹은 cookie에 저장됨(server 측의 session에 저장되는 방법과 대조)
- 사용자가 보호된 경로나 자원에 접근할 때마다 Bearer 스키마를 이용하여 Authorization header에 JWT를 보내야 함
- 서버의 보호된 경로는 Authorization header에서 유효한 JWT가 있는지 확인하여 있는 경우 자원에 엑세스 허용
장점
- 이 방법은 user의 상태를 server 메모리에 저장하지 않음으로써 stateless 한 인증 메커니즘
- 기존 토큰(OAuth : https://victorydntmd.tistory.com/4) 방식은 토큰이 의미 없는 문자열이기 때문에 이 토큰과 연관된 정보(사용자)를 탐색하여 사용자에 연관된 권한을 식별하여 권한 부여
- 하지만 JWT는 필수 정보(유저 정보 등)를 스스로 가지고 있어 DB에서 유저 정보를 조회하거나 로그인되어있는지 상태나 권한 조회 등 없이 바로 사용 가능
- 이는 DB 조회 횟수를 줄여 실제 운영에선 큰 성능 차이가 남
비교
- OAuth(random token)
- JWT(claim-based token)
- 결과적으로 차이점은 토큰을 생성하는 단계에서는 생성된 토큰을 별도로 서버에서 유지할 필요가 없음
- 토큰을 사용하는 API 서버 입장에서는 API 요청을 검증하기 위해서 토큰을 가지고 사용자 정보를 별도로 계정 시스템 등에서 조회할 필요가 없음
- 또한, 같은 claim-based token 방법인 XML 기반의 SAML 2.0보다 사이즈가 작고 구조가 단순하여 HTTP header에 넣기 편하며 여러 가지로 더 유리
JJWT(JAVA JWT 라이브러리)로 JWT 생성하는 예
- Issuer, Subject, Expiration, ID와 같은 토큰의 내부 권한 claims를 정의합니다.
- JWT를 암호화 서명을 해서 JWS를 만듭니다.
- JWT Compact Serialization 규칙에 따라 URL로 사용할 수 있도록 JWT를 압축합니다.
- 일반적으로 JWT를 생성해서 요청한 클라이언트로 전달합니다. 클라이언트는 토큰을 저장해 두고 서버로 요청할 때마다 전달하게 됩니다. 보통의 경우 쿠키 값이나 HTTP의 Authorization header에 담아 보내게 됩니다.
JWT/JWS/JWE
- JWT는 스스로 존재할 수 없는 abstract class 같은 개념
- 실제 구현은 JWS/JWE로 이루어짐
JWS
임의의 String 혹은 JSON에 서명하는 방법의 규격
- JWT는 서명됨으로써 결국 JWS이다.
- 암호화되지 않는 JWT header의 필수 claim은 alg claim뿐이다. alg claim은 해당 JWT를 signing and/or decrypting 하는 데 사용하는 알고리즘이다.
- 암호화되지 않는 JWT는 이 claim을 반드시 none으로 설정해야 한다.
JWS는 규격상 두 가지 방법으로 serialization 될 수 있음
1. JWS Compact Serialization
- JWT라고 부를 수 있음
- 디지털 서명 혹은 MAC(Message Authentication Codes)이 적용된 String
- 간결하고, URL-safe 한 문자열
- 단 하나의 서명만 있으며 Unprotected Header를 표현할 수 없음
2. JWS JSON Serialization
- 디지털 서명 혹은 MAC이 적용된 JSON object
- 간결하지 않으며, URL-safe하지 않음
- 두 개 이상의 서명이 가능하며 완전히 일반적인 문법
- 각 Header에 선택적으로 서명 적용 가능
- 구조
- payload : base64-url로 인코딩 된 내용, JSON일 필요 없음(필수 요소)
- signatures : JSON object의 array 형태, 각 object의 구성은 서명 하나와 관련된 메타데이터를 포함, 아래와 같음
- protected
- 서명에 의해 보호되어 무결성을 보장해야 하는 header 요소를 포함하는 JSON object
- base64-url로 인코딩
- 위의 예시에서 첫 번째 보호된 요소의 값을 base64-url로 디코딩하면 아래와 같음
- 각 signatures array 요소에 하나의 서명만 존재할 수 있음
- 필수 요소
- header
- unprotected header
- 서명으로 보호되지 않아 무결성을 보장하지 않는 header 요소를 포함하는 JSON object
- protected header와 header를 합치면 해당 서명(array 요소)의 전체 header
- 즉, 첫 번째 서명의 header는 {“alg”:”RS256", “kid”:”2010–12–29"}
- 필수 요소
- signature
- protected와 JWS payload로 계산된 서명의 base64-url 인코딩 된 값
- 필수 요소
- protected
- 예시
- 규격 7.2.2 - Flattened JWS JSON Serialization Syntax) - 5G N32 interface에서 사용
- 한 개의 서명/MAC이 있는 경우 문법이 flatten 될 수 있음
- signatures array가 없어지고 내용이 밖으로 나옴
- 서명 과정
- 서명될 내용(JSON일 필요 없음)을 base64-url 인코딩하여 payload 생성
- payload와 각 header에 몇 개의 서명이 필요한지 결정
- 보호될 header들을 포함한 JSON object 생성
- UTF-8 인코딩 된 보호될 header를 base64-url 인코딩하여 protected 생성
- 보호될 필요 없는 header들을 포함한 JSON object 생성하여 header 생성
- 각 protected와 unprotected header는 서명 하나의 암호 속성을 표현(JOSE Header)
- Issuer는 JOSE Header에 공개키의 힌트를 포함해야 함(jku, jwk, kid, x5u, x5c, x5t and x5t#s256)
- 각 서명을 계산하기 위해 형태 형성
- ASCII(BASE64URL-ENCODE(UTF8(JWS Protected Header of the corresponding entry)) ‘.’ BASE64URL-ENCODE(JWS Payload))
- 이전 단계에 alg요소(protected 혹은 unprotected에 포함)의 알고리즘에 따라 서명을 계산
- 이전 단계의 서명을 base64-url로 인코딩하여 하나의 signature 생성
- signatures 배열의 모든 signature를 위와 같은 방법으로 완료
- JWS 알고리즘 리스트(RFC 7518 - JWA)
- 가장 많이 사용되는 알고리즘
- HS256
- RS256
- ES256
- 가장 많이 사용되는 알고리즘
JWE
JSON 기반 데이터 구조를 encryption 하는 방법의 규격
JWE는 규격상 두 가지 방법으로 serialization 될 수 있음
1. JWE Compact Serialization
- JWE 토큰은 5가지 component로 구성
- Header
- JWS에 enc, zip 요소 추가
- enc : content encryption 알고리즘 정의, 이는 대칭 AEAD(Authenticated Encryption with Associated Data) 알고리즘이어야 함
- AEAD 암호화란?
- 인증만 수행하고 암호화되지 않는 값은 연관자료(Associated Data)로 칭함
- 하나의 메시지 전체를 인증하되 일부만 암호화해도 되는 경우가 있음
- 예) 패킷에서 보호하고자 하는 메시지를 담은 payload는 암호화, 최종 수신자에게 전달되는데 필요한 header는 그대로
- header가 변조되지 않았는지 인증 필요
- 예) 패킷에서 보호하고자 하는 메시지를 담은 payload는 암호화, 최종 수신자에게 전달되는데 필요한 header는 그대로
- 이를 위해 AEAD 고안
- 암호화 : AEAD_ENCRYPT(키, 평문, 연관자료) = 암호문, 연관자료, 인증 태그
- 평문 + 키 => 암호문(cyphertext)
- 연관자료 => 연관자료
- 키 + 평문 + 연관자료 => 인증 태그
- 복호화 : AEAD_DECRYPT(키, 암호문, 연관자료, 인증 태그) = 평문, 연관자료
- 평문과 연관자료가 손상된 경우, 인증 실패
- 여기서 평문, 연관자료 생략 가능
- 평문을 생략한 경우 => 일반 MAC
- 연관자료를 생략한 경우 => 일반 암호
- 암호화 : AEAD_ENCRYPT(키, 평문, 연관자료) = 암호문, 연관자료, 인증 태그
- 복호화와 무결성 검증을 단일 단계로 결합 => 데이터의 기밀성, 무결성, 인증을 동시에 보장하는 block cipher mode
- AEAD 암호화란?
- alg : CEK(Content Encryption Key)를 암호화하는 알고리즘 정의, 키 랩핑 알고리즘
- JWE Encrypted Key
- issuer가 header의 enc에 맞는 랜덤키(CEK)를 생성
- CEK가 header의 alg의 키 랩핑 알고리즘에 따라 암호화
- Initialization Vector(IV)
- 일부 암호화 알고리즘은 초기화 벡터가 필요
- 임의의 숫자로 키와 함께 데이터를 암호화하는 데 사용
- 데이터가 같은 암호로 반복적으로 암호화되는 것을 방지하기 위해 암호에 임의성을 부여
- 수신자가 복호화하는 데에도 필요
- 만약 초기화 벡터가 필요 없는 암호화 알고리즘이라면 공란
- JWE AAD(Additional Authentication Data)
- 암호화된 payload와 함께 무결성을 증명하는 데 사용될 추가 데이터
- Ciphertext
- 위에서 생성한 CEK와 header의 enc의 AEAD 알고리즘으로(IV, AAD를 이용하여) payload를 암호화한 값
- Authentication Tag
- header의 enc의 AEAD 알고리즘에 의해 ciphertext와 함께 생성됨
- ciphertext와 AAD의 무결성을 증명하는 데 사용
- AEAD에서 연관자료로 언급됨
- Header
- 서명 과정
-
-
- CEK 값을 결정하는 데 사용되는 알고리즘 파악
- header의 alg 요소에 의해 키 래핑 알고리즘 정의
- alg는 JWE 당 하나 존재
- 이전 단계의 키 관리 모드를 기반으로 CEK를 계산하고, JWE Encrypted Key를 계산
- CEK는 이후, JSON payload를 암호화하는 데 사용
- JWE Encrypted Key는 JWE 당 하나 존재
- 이전 단계에서 생성한 JWE Encrypted Key를 base64-url로 인코딩 JWE Encrypted Key 부분 생성
- JWE Initialization Vector에 임의의 값 생성, base64-url로 인코딩 Initialization Vector 부분 생성
- 토큰의 압축이 필요한 경우, zip header 요소에 정의된 알고리즘에 따라 일반 텍스트의 JSON payload 압축
- JSON 표현의 header를 UTF-8 인코딩과 base64url 인코딩 수행하여 JOSE Header 부분 생성
- JSON payload를 암호화하라면 이미 생성한 CEK, Initialization Vector와 AAD가 필요
- AAD는 이전 단계의 인코딩 된 JOSE Header의 ASCII 값을 계산하여 AAD 부분 생성
- header의 enc 요소에 정의된 알고리즘에 따라 CEK, Initialization Vector, AAD를 사용해 zip header 요소에 따라 압축된 JSON payload를 암호화
- enc의 AEAD 알고리즘의 결과로 Ciphertext와 Authentication Tag 생성
- 이전 단계의 Ciphertext를 base64-url로 인코딩하여 Ciphertext 부분 생성
- 이전 단계의 Authentication Tag를 base64-url로 인코딩하여 Authentication Tag 부분 생성
- CEK 값을 결정하는 데 사용되는 알고리즘 파악
- JWE 예시(평문 "The true sign of intelligence is not knowledge but imagination."을 수신자에게 암호화하는 예제)
- RSAES-OAEP 사용 : CEK(Content Encryption Key : 내용을 암호화하는 데 사용하는 키)를 암호화
- AES-GCM 사용 : 256bit키로 평문을 암호화하여 ciphertext, authentication tag를 생성
-
-
-
- protected header 생성 : {"alg":"RSA-OAEP","enc":"A256GCM"}
- 위의 protected header를 base64-url로 인코딩 : eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ
- 임의의 CEK 생성(256비트)
- RSAES-OAEP 사용해서 수신자의 공개키로 CEK 암호화
- 암호화된 CEK를 base64-url로 인코딩하여 JWE Encrypted Key 생성
- 임의의 Initialization Vector 생성 및 base64-url로 인코딩
- AAD 생성 : ASCII(BASE64URL(UTF8(JWE Protected Header)))
- 평문에 AES-GCM으로 암호화(CEK를 키로 사용) : 결과로 ciphertext, Initialization Vector, AAD, authentication tag 받음
- ciphertext를 base64-url로 인코딩
- authenticatino tag를 base64-url로 인코딩
- .(dot)를 사이에 두고 이어 붙임 BASE64URL(UTF8(JWE Protected Header)) || '.' || BASE64URL(JWE Encrypted Key) || '.' || BASE64URL(JWE Initialization Vector) || '.' || BASE64URL(JWE Ciphertext) || '.' || BASE64URL(JWE Authentication Tag)
- 결과
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ. OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe ipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDb Sv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaV mqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je8 1860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi 6UklfCpIMfIjf7iGdXKHzg. 48V1_ALb6US04U3b. 5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6ji SdiwkIr3ajwQzaBtQD_A. XFBoMYUZodetZdvTiFvSkQ
-
2. JWE JSON Serialization
- JWE Compact Serialization과 달리 동일한 JSON payload를 여러 수신자에게 타겟팅된 암호화 데이터로 생성 가능
- JWE JSON Serialization에서는 6개의 component로 구성
- protected
- AEAD에 의해 보호되어 무결성을 가진 header 요소를 포함하는 JSON object
- JWE 토큰의 모든 수신자에게 적용
- base64-url로 인코딩 된 JSON object
- unprotected(shared)
- 무결성 보호되지 않는 header 요소를 포함하는 JSON object
- JWE 토큰의 모든 수신자에게 적용
- recipients
- 무결성 보호되지 않는 header 요소가 포함된 JSON object
- 특정 수신자에게만 적용
- 구성
- header : 각 수신자에 대해 보호되지 않는 header
- encrypted_key : 암호화된 키를 base64-url로 인코딩, payload를 암호화하는 데 사용되는 키
- iv : 암호화를 위해 사용되는 Initialization Vector가 base64-url 인코딩 값
- ciphertext : ciphertext가 base64-url 인코딩 된 값
- tag : AEAD 암호화 결과로 나온 authenticated tag의 base64-url 인코딩 된 값
- protected
- 예시
- 규격 7.2.2 - Flattened JWE JSON Serialization Syntax) - 5G N32 interface에서 사용
- 한 개의 서명/MAC이 있는 경우 문법이 flatten 될 수 있음
- repicients array가 없어지고 내용이 밖으로 나옴
- 서명 과정
-
- CEK 값을 결정하는 데 사용되는 알고리즘을 파악
- header의 alg 요소에 의해 정의
- alg는 protected, unprotected, per-recipient header에 포함될 수 있으며, 세 번째인 경우 수신자별로 alg를 정의 가능
- 이전 단계의 키 관리 모드를 기반으로 CEK를 생성하고, alg 알고리즘으로 암호화
- CEK는 이후, JSON payload를 암호화하는 데 사용
- 이전 단계에서 암호화된 CEK를 base64-url로 인코딩하여 JWE Encrypted Key 부분 생성
- 각 수신자마다 이전 3단계를 수행
- 각 recipients array의 요소로 생성
- JWE Initialization Vector에 임의의 값 생성, base64-url로 인코딩하여 Initialization Vector 부분 생성
- 토큰의 압축이 필요한 경우, zip header 요소에 정의된 알고리즘에 따라 일반 텍스트의 JSON payload 압축
- zip은 protected나 shared-unprotected에 정의
- protected, shared-unprotected, per-repicient unprotected header를 표현하는 JSON 생성
- UTF-8로 인코딩 된 protected는 base64-url로 인코딩하여 protected 부분 생성
- protected는 optional
- 있다면 하나만 존재 가능
- 없다면 empty element
- protected는 optional
- optional 단계 : 무결성을 보장하길 원하는 추가 데이터(AAD)를 정하고, 이를 base64-url로 인코딩하여 AAD 부분 생성, 이후에 payload를 암호화하는 데 사용
- JSON payload를 암호화하라면 이미 생성한 CEK, Initialization Vector와 AAD가 필요
- AAD는 인코딩 된 protected header의 ASCII 값을 계산하여 AAD 부분 생성하여 사용
- ASCII(Encoded Protected Header)
- 하지만 만약 optional 단계를 수행하여 AAD가 이미 존재한다면 AAD의 값은 아래와 같음
- ASCII(encoded JWE Protected Header ‘.’ BASE64URL-ENCODE(AAD ))
- AAD는 인코딩 된 protected header의 ASCII 값을 계산하여 AAD 부분 생성하여 사용
- 압축된 payload를 CEK와 Initialization Vector, AAD를 이용해 enc header에 정의된 알고리즘으로 암호화
- enc의 AEAD 알고리즘의 결과로 Ciphertext와 Authentication Tag 생성
- 이전 단계의 Ciphertext를 base64-url로 인코딩하여 Ciphertext 부분 생성
- 이전 단계의 Authentication Tag를 base64-url로 인코딩하여 Authentication Tag 부분 생성
- CEK 값을 결정하는 데 사용되는 알고리즘을 파악
- JWE 알고리즘 리스트(RFC 7518 - JWA)
최대한 자세한 정보를 담아내다 보니 글이 좀 정신없어졌습니다.
자세한 이해를 위해 예제와 그에 대한 설명들을 정리했습니다.
JOSE 공부에 도움이 되길 바라겠습니다:)
https://medium.facilelogin.com/jwt-jws-and-jwe-for-not-so-dummies-b63310d201a3
https://medium.com/@neodey/jwt-jose-peeping-under-the-hood-part-2-3ac50326d3e3
https://connect2id.com/products/nimbus-jose-jwt/algorithm-selection-guide
https://connect2id.com/products/nimbus-jose-jwt/examples/signed-and-encrypted-jwt
https://www.linkedin.com/pulse/beneffits-jwtjwsjwe-api-designs-wagner-brunca/
https://stackoverflow.com/questions/46958289/nested-jws-jwe-vs-jwe-with-authenticated-encryption
'Tech > Other' 카테고리의 다른 글
GitHub Copilot 후기 (0) | 2021.11.26 |
---|---|
vMotion(Live Migration) (0) | 2021.11.23 |
Duplex, Duplication(이중화) (0) | 2021.10.26 |
CPU는 어떻게 동작할까 (2) | 2021.08.03 |