해시는 바뀜을, 서명은 누가를 보증한다
한 줄 요약
반입은 "파일을 옮기는 일" 이 아니라 요청서 → 받기 → 목록 → 서명 → 매체 → 검증 → 설치 → 기록 의 절차다. 해시는 옮기는 사이 바뀌었는지를, 서명은 그 목록을 누가 만들었는지를 보증한다. 둘 중 하나만 있으면 반쪽이다.
왜 이게 필요했나
폐쇄망 반입 심의에서 흔히 오가는 것은 USB 하나와 메일 한 통이다. USB 안에 해시 목록 파일이 함께 들어 있으면 안심하는데, 그 목록은 파일을 바꾼 사람이 같이 고칠 수 있다. 전송 중 손상은 잡지만 바꿔치기는 못 잡는다. 반대로 서명만 하고 해시 목록이 없으면 무엇에 서명했는지가 모호하다. 그리고 심사 쪽이 "버전은 최신으로" 라는 요청서를 받으면, 바깥에서 받은 날과 심의 날 사이에 새 버전이 나와 목록과 실물이 어긋난다.
어떻게 동작하나
요청서는 정확한 버전으로. requests 가 아니라 requests==2.32.3, rsc.io/quote 가 아니라 v1.5.2. 범위나 "최신" 을 적으면 받는 날마다 결과가 달라지고, 심의 기록과 반입물이 일치한다는 것을 아무도 증명할 수 없다. 의존성은 요청서에 사람이 적는 것이 아니라 받는 도구가 풀어 준 결과(pip 의 휠 목록, Go 의 go.sum)를 목록에 붙인다.
목록은 상대 경로로. sha256sum 은 목록에 적힌 경로를 현재 디렉터리 기준으로 연다. 번들 디렉터리 안에서 find . -type f 로 만들면 번들을 어디에 풀든 그 안에서 sha256sum -c 가 성립한다. 목록 파일 자신과 서명 파일은 목록에서 뺀다.
서명은 목록에 한다. 파일 수백 개에 하나하나 서명할 필요 없이, 해시 목록 한 장에 서명하면 목록이 모든 파일을 묶는다. OpenSSL 3 의 genpkey -algorithm ed25519 로 키를 만들고 pkeyutl -sign -rawin 으로 서명, pkeyutl -verify -pubin -rawin -sigfile 로 검증한다(Ed25519 는 해시를 따로 고르지 않고 원문을 그대로 받는다). GPG 나 minisign 을 쓰는 조직도 많은데 원리는 같다.
개인 키와 공개 키는 다른 길로. 개인 키는 받는 쪽 담당자 PC 에서 나오지 않는다. 공개 키는 매체가 아닌 경로(사전 등록, 별도 문서)로 폐쇄망 쪽에 미리 가 있어야 한다. 매체에 공개 키를 같이 넣으면 바꿔치기한 사람이 자기 공개 키를 넣으면 그만이다.
검증 순서는 서명 먼저. 폐쇄망 쪽은 ① 미리 등록된 공개 키로 목록의 서명을 확인하고 ② 그 목록으로 파일 해시를 확인한다. 순서를 뒤집어 해시부터 보면, 바꿔치기된 목록이 자기 파일들과 멀쩡히 맞는 것을 "통과" 로 읽게 된다. 그리고 검증 스크립트는 실패할 때 0 이 아닌 값으로 끝나야 다음 단계(설치)가 멈춘다. 메시지만 찍고 0 으로 끝나는 검증은 자동화에서 검증이 아니다.
현장에서 만나는 모습
이 실습 이미지에서 requests 2.32.3(휠 5개)과 rsc.io/quote v1.5.2(모듈 zip 3개, 대부분 x/text 4.8MB)를 한 번들로 묶으면 파일이 48개, 5MB 남짓이었다. 휠 5개와 모듈 zip 3개 말고도 Go 의 cache/download 에 쌓이는 보조 파일(.mod·.info·.ziphash 와 체크섬 데이터베이스 조회 기록)이 함께 들어가기 때문이다. 목록은 사람이 아니라 find 가 만들어야 하는 이유다. 여기에 SHA256SUMS 한 장과 64바이트짜리 Ed25519 서명 한 장이 붙는다. 채점기는 여러분의 검증 스크립트를 번들 사본 두 개에 돌린다. 하나는 휠 한 개의 한 바이트를 바꾼 것(해시에서 걸려야 한다), 다른 하나는 바꾼 파일에 맞춰 SHA256SUMS 까지 고친 것(서명에서 걸려야 한다). 두 번째를 통과시키는 스크립트는 바꿔치기를 막지 못한다.
기록도 절차의 일부다. 무엇을 요청했고, 번들에 파일이 몇 개였고, 목록 자체의 해시가 무엇이고, 어떤 키로 서명을 확인했고, 무엇을 설치했는지를 남겨야 나중에 "그때 들어온 것이 무엇이었나" 에 답할 수 있다.
다음 실습에서 할 것
요청서를 쓰고, 파이썬 휠과 Go 모듈을 한 번들로 받아 SHA256SUMS 와 Ed25519 서명을 붙인다. 개인 키를 뺀 매체(tar)를 만들어 폐쇄망 쪽에 풀고, 서명 먼저·해시 다음 순서의 검증 스크립트를 쓴다. 채점기가 그 스크립트를 바꿔치기한 사본에 돌려 본다. 마지막에 바깥을 막은 채 번들만으로 설치하고 반입 기록을 JSON 으로 남긴다.
참고 문서: OpenSSL genpkey · OpenSSL pkeyutl · GNU coreutils sha256sum · pip download · Go Modules Reference