1. MMTLS 프로토콜 개요
MMTLS(MicroMessenger Transport Layer Security)는 위챗(WeChat)에서 자체 개발한 전송 계층 보안 프로토콜로, TLS 1.3을 기반으로 모바일 메신저 환경에 맞게 최적화되었습니다. 주요 특징은 다음과 같습니다:
- 사전 보안(Forward Secrecy): 세션마다 임시 ECDH 키를 사용하여 키 교환
- 저지연 설계: 단 1-RTT로 핸드셰이크 완료 (기존 TLS 1.3 대비 성능 향상)
- 중간자 공격 방지: 이중 인증서 검증과 고정된 공개키 기반의 서명 확인
2. 핸드셰이크 절차
2.1 네 단계의 상호 인증 과정
sequenceDiagram
Client->>Server: ClientHello (ECDH 공개키 쌍 포함)
Server->>Client: ServerHello (키 쌍 선택)
Server->>Client: CertificateVerify (서버 서명 데이터 제공)
Server->>Client: Finished (서버측 핸드셰이크 완료 신호)
Client->>Server: Finished (클라이언트 확인 응답)
이 프로세스는 클라이언트와 서버가 서로의 신원을 확인하고, 안전한 대칭 키를 도출하는 데 사용됩니다.
2.2 키 생성 로직 구현 예시
func (c *SecureClient) GenerateSessionKey() {
// P-256 곡선 기반 ECDH 키 쌍 생성
primaryPriv, primaryPub := crypto.GenerateECDHKeyPair()
backupPriv, backupPub := crypto.GenerateECDHKeyPair()
// 서버의 공개키로부터 공유 비밀 생성
sharedSecret := crypto.ComputeECDHSecret(primaryPriv, serverPublicKey)
// HKDF-SHA256을 이용한 키 파생
derivedKey := hkdf.Expand(sha256.New, sharedSecret, []byte("mmtls-key-expand"))
c.sessionKey = derivedKey[:32] // AES-128-GCM에 적합한 길이로 잘라 사용
}
3. 보안 구성 요소
3.1 암호화 스위트 구성
| 구성 요소 | 사용 알고리즘 | 키 길이 |
|---|---|---|
| 키 교환 | ECDHE (P-256) | 256비트 |
| 대칭 암호화 | AES-128-GCM | 128비트 |
| 해시 함수 | SHA-256 | 256비트 |
| 키 확장 | HKDF-SHA256 | 가변 길이 |
3.2 재전송 공격 방어 메커니즘
각 패킷에 시퀀스 번호 기반 Nonce 변조를 적용하여 동일한 키로 반복 전송되는 요청을 차단합니다.
func buildIVWithSequence(baseIV []byte, seq uint32) []byte {
iv := make([]byte, len(baseIV))
copy(iv, baseIV)
seqBytes := binary.BigEndian.AppendUint32(nil, seq)
for i := 0; i < 4; i++ {
iv[12+i] ^= seqBytes[i] // 초기화 벡터의 일부를 시퀀스로 변형
}
return iv
}
4. 패킷 형식과 데이터 처리
4.1 헤더 구조 정의
struct MMTLSPacket {
uint8_t contentType; // 0x16: 핸드셰이크, 0x17: 데이터
uint16_t magicTag; // 0xF103 (프로토콜 식별자)
uint16_t payloadLen; // 페이로드 길이
uint8_t payload[]; // 암호화된 본문
};
4.2 데이터 직렬화 및 암호화
애플리케이션 데이터는 Protobuf를 통해 직렬화되고, AES-GCM으로 인증 암호화됩니다.
func EncryptPayload(data proto.Message, session *SessionState) ([]byte, error) {
plaintext, err := proto.Marshal(data)
if err != nil {
return nil, err
}
nonce := buildIVWithSequence(session.nonceSeed, session.sequenceID)
aad := generateAAD(session.sessionID, session.sequenceID)
ciphertext, err := aesgcm.Seal(nil, nonce, plaintext, aad)
if err != nil {
return nil, err
}
session.sequenceID++
return ciphertext, nil
}
5. 성능 최적화 전략
- 세션 재사용:
NewSessionTicket메시지를 통해 PSK(Pre-Shared Key) 저장 및 후속 연결에서 0-RTT 복구 지원
func handleSessionTicket(ticketData []byte, state *SessionState) {
psk := extractPSKFromTicket(ticketData)
state.pskCache = append(state.pskCache, psk)
}
- 연결 풀 관리: HTTP 클라이언트에서 TCP 연결 재사용을 통해 지연 감소
transport := &http.Transport{
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
}
client := &http.Client{Transport: transport}
6. 추가 보안 강화 조치
- 공개키 고정(Public Key Pinning): 클라이언트에 위챗 서버의 ECDSA 공개키를 하드코딩하여 피싱 공격 방지
func verifyServerSignature(payload, sig []byte) bool {
fixedPubKey := loadTrustedPublicKey() // 미리 배포된 공개키 사용
hash := sha256.Sum256(payload)
r, s := decodeSignature(sig)
return ecdsa.Verify(fixedPubKey, hash[:], r, s)
}
- 디바이스 바인딩 방지:
ClientRandom필드에 무작위 난수 삽입하여 디바이스 프로파일링 난이도 증가
hello.Random = append(hello.Random, randBytes(32)...)
7. 일반적인 오류 진단
오류 코드 -13 발생 시 점검 항목:
- 시스템 시간 동기화 여부 확인 (±30초 이내 권장)
- 서버 인증서 체인 유효성 검사
- ECDH 개인키 재사용 여부 점검 (매 세션 새로 생성 필요)