boho: 설계 배경과 기초 원리
boho는 개발자가 직접 관리하는 장치와 애플리케이션 사이에 암호화와 인증을 적용하기 위한 공유 키 기반 라이브러리입니다. 이름은 ‘보호’에서 왔습니다. Arduino용 C/C++ 구현과 Node.js·브라우저용 JavaScript 구현을 제공하므로, DIY 장치에서 웹앱까지 같은 방식으로 암호화 메시지를 주고받을 수 있습니다.
boho의 출발점은 다음 질문입니다.
통신할 양쪽의 코드를 직접 관리하고 비밀 키도 미리 설정할 수 있다면, 연결할 때마다 공개키 기반의 신뢰 체계를 구성해야 할까?
이런 환경에서는 사전 공유 키를 사용하는 대칭키 방식으로도 필요한 암호통신을 구성할 수 있습니다. boho는 이 전제를 바탕으로 SHA-256, XOR 연산, 공유 키 인증, 바이너리 메시지 형식을 하나의 작은 구성으로 묶습니다.
이 문서는 boho가 필요한 이유와 기초 원리를 소개합니다. IOSignal의 접속 인증 설정은 인증을, 실제 메서드 사용은 클라이언트 API를 참고하세요. 대칭키 방식이 적합한 환경이라는 판단과 현재 boho 구현의 보안 강도에 대한 평가는 별개입니다. boho는 표준 SHA-256을 사용하는 자체 프로토콜이며, 그 조합 전체가 표준 암호 방식으로 검증되었다는 뜻은 아닙니다.
1. 이미 많은 암호 기술이 있는데 왜 만들었을까?
암호 알고리즘이 존재하는 것과 작은 장치에서 실제 암호통신을 구현하는 것은 다른 문제입니다. 개발자는 암호화 외에도 상대 인증, 키 설정, 메시지 형식, 통신 라이브러리, 메모리 사용량, 배포 후 관리를 연결해야 합니다.
예를 들어 Arduino로 만든 센서와 직접 작성한 웹앱을 연결한다고 생각해 봅시다. 개발자는 양쪽 프로그램을 알고 있고, 장치에 키를 기록할 수 있으며, 웹앱에도 별도 절차로 같은 키를 설정할 수 있습니다. 이 경우 처음 만나는 불특정 상대와 신뢰 관계를 만드는 기능보다, 이미 정한 상대끼리 키를 확인하고 데이터를 보호하는 기능이 먼저 필요합니다.
boho는 이런 개발 환경에 초점을 맞춥니다.
- 장치와 애플리케이션이 미리 공유한 키로 상대를 인증합니다.
- SHA-256을 바탕으로 데이터 길이에 맞는 키스트림을 생성하고 XOR로 암호화합니다.
- Arduino, Node.js, 브라우저에서 서로 호환되는 메시지 형식을 사용합니다.
- 암호화 처리를 전송 수단과 분리하여 TCP, WebSocket, 직렬 통신, MQTT 페이 로드 등에 적용할 수 있게 합니다.
- 연결 단위의 보호와 별도 데이터 키를 사용하는 종단간 암호화를 제공합니다.
차별점은 XOR나 공유 키라는 개념 자체의 새로움에 있지 않습니다. 직접 만든 임베디드 장치와 웹 환경을 같은 암호화·인증 방식으로 연결하는 구현과 사용 방식에 있습니다. 연결 관리와 메시지 전달까지 필요한 경우에는 boho를 사용하는 IOSignal을 함께 활용할 수 있습니다.
2. 공개키 교환 없이도 통신할 수 있는 환경
대칭키 암호화는 양쪽이 같은 비밀 키를 가지고 있다는 전제에서 출발합니다. 핵심 과제는 최초에 그 키를 어떻게 안전하게 전달하고, 이후 어떻게 보관·교체할 것인가입니다. 이 과제를 다른 경로로 해결할 수 있다면 모든 통신 연결에 공개키 교환을 넣을 필요는 없습니다.
| 환경 | 공유 키를 설정하는 방법의 예 | 대칭키 방식이 맞는 이유 |
|---|---|---|
| 직접 제작하는 임베디드 장치 | 제작·설치 시 USB, 직렬 연결, 신뢰할 수 있는 설정 도구로 장치별 키 주입 | 개발자가 장치와 접속 서버를 모두 관리함 |
| 직접 개발하는 웹앱과 개인 장치 | 사용자가 별도 경로로 받은 장치 키를 웹앱에 입력하거나 신뢰할 수 있는 페어링 절차로 등록 | 통신 대상이 정해져 있고 최초 키 공유를 직접 관리할 수 있 음 |
| 관리 주체가 같은 서버 사이 | SSH, 기존 TLS 연결, 비밀 관리 시스템 등으로 키 배포 | 이미 보호된 관리 경로가 존재함 |
| 제한된 장치·사용자 그룹 | 장치 또는 통신 상대별로 키를 발급하고 폐기·교체 절차를 운영 | 불특정 상대를 위한 공개 신뢰 체계가 필수 조건이 아님 |
이런 환경에서는 충분한 엔트로피를 가진 키와 적절한 인증·메시지 보호 방식을 사용하여, 사전 공유 대칭키만으로도 안전한 통신을 설계할 수 있습니다. 다만 키를 공유한 주체는 같은 자격을 갖습니다. 여러 장치에 하나의 키를 넣으면 어느 장치가 보낸 것인지 키만으로 구분할 수 없고, 한 장치의 키 유출이 다른 장치에도 영향을 줍니다.
‘신뢰하는 제3자가 필요 없다’는 말은 인증기관이 없어도 개발자가 직접 키를 배포하고 상대를 정할 수 있다는 뜻입니다. 신뢰가 사라지는 것이 아니라, 인증기관 대신 키를 설정하는 경로와 양쪽 프로그램을 신뢰하는 구조입니다.
웹앱에서는 코드를 직접 작성한다는 사실만으로 키가 비밀이 되지는 않습니다. 공개 JavaScript 번들에 공통 비밀 키를 넣으면 방문자가 그 키를 읽을 수 있습니다. 키 설정 방법과 함께 웹앱 코드 자체의 안전한 전달도 필요합니다.
3. XOR: 아주 단순한 연산으로 암호화와 복호화하기
XOR는 두 비트가 다르면 1, 같으면 0을 만드는 연산입니다. 같은 값으로 두 번 XOR하면 원래 값으로 돌아온다는 성질이 있습니다.
암호화: 암호문 = 평문 XOR 키스트림
복호화: 평문 = 암호문 XOR 키스트림
(평문 XOR 키스트림) XOR 키스트림 = 평문
1바이트 예제로 보면 다음과 같습니다.
평문 01000001 0x41, 문자 A
키스트림 10100110 0xA6
--------
암호문 11100111 0xE7
암호문 11100111
키스트림 10100110
--------
복호화 01000001 0x41, 문자 A
연산은 매우 단순하지만, 적절한 키스트림과 결합하면 강력한 암호화의 기반이 됩니다. 안전성을 결정하는 것은 XOR의 복잡도가 아니라 키스트림을 어떻게 만들고 사용하는가입니다.
진짜 OTP의 강점과 현실적인 부담
일회용 패드(One-Time Pad, OTP)는 평문과 독립적인 완전한 무작위 비트열을 키로 사용합니다. 키가 데이터만큼 길고, 비밀로 유지되며, 한 번만 사용된다는 조건을 만족하면 암호문만으로 평문을 판별할 수 없는 완전한 비밀성을 제공합니다. 여기서 OTP는 일회용 로그인 비밀번호가 아니라 ‘일회용 패드’를 뜻합니다.
문제는 키의 크기입니다. 1MB 데이터를 보내려면 양쪽이 1MB의 새 무작위 키를 안전하게 공유해야 합니다. 다음 1MB를 보낼 때는 또 다른 키가 필요합니다. 지속적으로 통신하는 작은 장치에서 이런 키를 미리 저장하고 공급하기는 어렵습니다.
같은 패드를 재사용하면 다음 관계가 드러납니다.
C1 = P1 XOR S
C2 = P2 XOR S
C1 XOR C2 = P1 XOR P2
키스트림 S가 없어지고 두 평문의 관계가 노출됩니다. 따라서 짧은 고정 키를 반복해서 XOR하는 방식과 OTP는 전혀 다른 안전성을 가집니다.
4. 해시 출력으로 ‘가상 OTP’를 만들기
SHA-256은 입력을 256비트, 즉 32바이트의 해시값으로 변환합니다. 동일한 입력은 동일한 결과를 만들며, 입력을 바꾸면 출력도 크게 달라집니다. SHA-256 자체는 데이터를 복호화할 수 있는 암호 알고리즘이 아니라 해시 알고리즘입니다. SHA-256의 표준 정의: NIST FIPS 180-4
충분히 예측하기 어려운 비밀 입력을 해시에 넣으면 그 출력을 키스트림의 재료로 사용하는 구성을 생각할 수 있습니다. 여기에 메시지마다 달라지는 값과 블록 번호를 결합하면 필요한 길이만큼 출력을 생성할 수 있습니다.
공유한 비밀 키 + 메시지별 값
↓
해시 기반 생성 과정
↓
블록 1 | 블록 2 | 블록 3 | …
↓
데이터 길이만큼 키스트림 사용
↓
평문과 XOR
송신자와 수신자가 같은 키와 메시지별 값을 사용하면 같은 키스트림을 재현할 수 있으므로, 데이터 전체 길이에 해당하는 패드를 따로 전송하거나 저장할 필요가 없습니다.
다만 공개된 랜덤 값만 해시해서는 암호화용 비밀 키스트림이 되지 않습니다. 누구나 같은 값을 계산할 수 있기 때문입니다. 랜덤 값이나 nonce는 메시지를 구분하는 재료이고, 키스트림을 비밀로 만드는 핵심 입력은 공 유 비밀 키입니다. 해시는 부족한 비밀 정보나 약한 비밀번호에 새로운 엔트로피를 만들어 주지도 않습니다.
이 문서에서 ‘가상 OTP’ 또는 ‘유사 OTP’는 이렇게 생성한 의사난수 키스트림을 설명하기 위한 표현입니다. 짧은 비밀로 긴 출력을 생성하므로 진짜 OTP의 정보이론적 안전성을 갖지는 않습니다. 안전성은 키의 강도, 생성 방식, 입력의 재사용 여부와 전체 프로토콜에 의존합니다.
키스트림을 생성하여 XOR한다는 큰 구조는 스트림 암호에서도 사용됩니다. 예를 들어 ChaCha20도 키·nonce·카운터로 키스트림을 만들고 평문과 XOR합니다. 다만 boho의 SHA-256 조합과 ChaCha20은 서로 다른 구성입니다. RFC 8439 §2.4
5. boho는 이 원리를 어떻게 구현하는가?
아래 내용은 JavaScript boho 2.2.0과 Arduino boho 0.8.0의 구현을 기준으로 합니다. 기호 ||는 바이트열 연결을 뜻합니다.
5.1 공유 키와 메시지별 값으로 시작점 만들기
일반적 인 set_key 경로에서는 입력 키를 SHA-256으로 해시하여 32바이트 내부 키를 만듭니다. 이 키에 12바이트 salt12를 붙여 다시 해시합니다.
K = SHA256(입력 키) // set_key 경로
B = SHA256(K || salt12) // 메시지의 키스트림 생성 기준값
salt12의 구성은 다음과 같습니다.
| 구성 요소 | 크기 | 역할 |
|---|---|---|
| 초 단위 시각 | 4바이트 | 메시지별 시간 정보 |
| 밀리초 | 2바이트 | 시간 정보의 세분화 |
| 카운터 | 2바이트 | 가까운 시각에 생성한 메시지 구분 |
| nonce | 4바이트 | 메시지 또는 연결에 관련된 구분 값 |
이 값들은 비밀 키가 아닙니다. 수신자가 같은 키스트림을 재현하기 위해 메시지 또는 인증 과정에서 얻는 정보입니다. 같은 내부 키 아래에서 전체 salt12가 반복되면 같은 키스트림이 만들어지므로, 메시지·방향·장치·재시작을 가로지르는 중복 여부가 중요합니다.