Quick Reference
const a = '{"name":"Kim","id":1}';
const b = '{\n "id": 1,\n "name": "Kim"\n}';
// text 자체 hash는 다름
hash(a) !== hash(b);
// 같은 canonicalization 규칙을 적용한 뒤 hash해야 안정적
hash(canonicalize(JSON.parse(a))) === hash(canonicalize(JSON.parse(b)));직렬화 기준
JSON 값이 같아도 text가 같다는 뜻은 아니다
JSON은 공백, 줄바꿈, object member 순서에 의미를 두지 않습니다. 두 문서가 같은 데이터를 표현하더라도 byte 단위 text는 다를 수 있습니다. hash, 서명, 캐시 키를 JSON text에 바로 걸면 같은 데이터가 다른 결과를 만들 수 있습니다.
{"id":1,"name":"Kim"}{
"name": "Kim",
"id": 1
}위 두 문서는 데이터 의미상 같게 다뤄질 수 있지만 문자열은 다릅니다. 그래서 서명이나 hash에는 "어떤 JSON text를 기준으로 삼을지"를 먼저 고정해야 합니다.
canonicalization은 하나의 표준 표현으로 정렬하는 단계다
canonicalization은 같은 JSON 데이터를 항상 같은 byte sequence로 직렬화하기 위한 규칙입니다. 공백 제거, object member 정렬, 문자열 escape 방식, 숫자 표현 같은 세부 사항을 고정합니다. RFC 8785의 JSON Canonicalization Scheme은 이런 목적의 표준화된 방식입니다.
입력 JSON
-> parse
-> canonicalization 규칙으로 다시 serialize
-> hash 또는 signature중요한 점은 "대충 JSON.stringify() 한 번 호출"이 항상 canonicalization이라는 뜻은 아니라는 것입니다. JavaScript의 property 열거 순서는 규정되어 있지만, 객체가 만들어진 순서에 영향을 받고 다른 언어의 serializer와 동일하다는 보장이 없습니다. 숫자 출력과 Unicode escape 처리까지 고정해야 언어 간 검증이 가능합니다.
RFC 8785가 필요한 경계와 단순 key 정렬의 경계를 구분한다
단순 key 정렬은 한 런타임 안에서 캐시 키를 안정화하는 데는 쓸 수 있습니다. 그러나 외부 서명이나 다른 언어와의 hash 비교에는 RFC 8785 JCS처럼 문자열, 숫자, member 정렬 규칙을 모두 고정한 방식이 필요합니다. hash 대상이 protocol 계약이라면 canonicalizer 이름과 버전, UTF-8 byte 인코딩, hash 알고리즘까지 함께 고정합니다.
object member 순서에 의존하면 구현 차이에 흔들린다
RFC 8259 기준에서 object는 순서를 가진 record가 아닙니다. 어떤 parser는 입력 순서를 보존하고, 어떤 serializer는 key를 정렬하고, 어떤 구현은 map 내부 순서에 따라 출력합니다. 사람이 보기 위한 JSON에서는 큰 문제가 아니지만 hash나 서명에서는 치명적입니다.
const value = { b: 2, a: 1 };
JSON.stringify(value); // 구현과 생성 과정에 따라 기대 순서를 고정하기 어려움stable hash가 필요하면 object key 정렬 규칙을 명시한 canonical serializer를 써야 합니다. 단순히 "현재 런타임에서 이렇게 나오더라"를 계약으로 삼으면 다른 언어나 버전에서 깨질 수 있습니다.
선택 기준
| 목적 | 선택 |
|---|---|
| 사람에게 보여 줄 pretty JSON | JSON.stringify(value, null, 2) 같은 포맷 |
| API 응답 전송 | 일반 serializer, 계약은 schema로 관리 |
| hash, signature, Merkle tree | canonical JSON 규칙 고정 |
| 언어 간 검증이 필요 | RFC 8785 같은 공개 규칙 사용 |
| 숫자 정밀도가 중요 | number 대신 string 또는 정수 단위 고려 |
주의사항
pretty print 결과를 서명 대상으로 삼으면 공백만 달라져도 서명이 깨집니다. 서명 대상은 사람이 보기 좋은 형식이 아니라 기계가 항상 재현할 수 있는 canonical byte sequence여야 합니다.
{ "id": 1, "name": "Kim" }{
"id": 1,
"name": "Kim"
}canonicalization 전에 중복 key가 있는 JSON을 허용하면 결과가 정의되지 않을 수 있습니다. parser마다 어느 값을 남길지 다를 수 있으므로, 서명이나 hash 대상 JSON은 생성 단계에서 중복 key를 차단해야 합니다.
JSON.parse() 뒤에 canonicalize하면 이미 중복 key가 소실됐을 수 있습니다. 서명 검증 입력에서는 중복 name을 거부하는 parser와 제한된 JSON profile을 먼저 선택해야 합니다.
참고 링크
2 sources