MMTLS 프로토콜 분석 및 안전한 통신 구현

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. 성능 최적화 전략

  1. 세션 재사용: NewSessionTicket 메시지를 통해 PSK(Pre-Shared Key) 저장 및 후속 연결에서 0-RTT 복구 지원
func handleSessionTicket(ticketData []byte, state *SessionState) {
    psk := extractPSKFromTicket(ticketData)
    state.pskCache = append(state.pskCache, psk)
}
  1. 연결 풀 관리: 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 발생 시 점검 항목:

  1. 시스템 시간 동기화 여부 확인 (±30초 이내 권장)
  2. 서버 인증서 체인 유효성 검사
  3. ECDH 개인키 재사용 여부 점검 (매 세션 새로 생성 필요)

태그: MMTLS Go 언어 암호화 프로토콜 ECDH AES-GCM

6월 1일 22:37에 게시됨