Q> HTTPS를 적용하는 웹사이트에서 서버의 공개키로 데이터 암호화 적용을 어떻게 하나요?

공인인증서로 HTTPS를 적용하고 있는 웹사이트가 있는데, 클라이언트의 브라우저에서 서버의 공개키로 일부 데이터를 암호화해서 서버에 전달하도록 하고 싶습니다. 어떻게 해야 하나요?

A>

간혹, 모바일앱이나 웹사이트 모의해킹 진단을 하다보면, 서버의 공개키를 이용해서 데이터를 암호화하지 않고 임의의 대칭키방식으로 데이터를 암호화해서 서버에 전송하는 경우가 있습니다.
대부분의 대칭키 방식은 클라언트에 키가 있기 때문에 이를 복호화하는 것은 시간 문제일 뿐입니다.
어떤 경우에는 그 키를 그냥 base64로 인코딩하거나 shift기법 또는 XOR로 쓰는 경우도 있어서 공격자 관점에서 복호화가 아주 쉬웠던 경우도 있습니다. 이런 취약점은 아주 오래전부터 심심치 않게 발견됩니다.
잘 아시는 것 처럼, 데이터를 암호화할때 고려해야 할 중요한 요소는 키의 관리입니다.
키를 생성해서 잘 보관하는 것, 안전하게 키를 상호 교환하는 것, 키분실시 대응 방안(키의 변경관리) 등을 키관리입니다.

여기에서는 대칭키를 클라이언트에 내려줘서 키관리의 부담을 가져가지 말고, 서버의 인증서에서 공개키를 축출하여 이 공개키로 데이터를 암호화하여 서버에 전송하는 방법의 파이썬 간단한 예제코드를 보여드립니다.

아래의 간단한 파이썬 코드는 클라이언트에서 서버의 공개키를 이용하여 데이터를 암호화하여 서버에 전송하는 예제입니다.

[] 참고 사항
- 이 코드는 예제 이기 때문에 실무에 적용시에는 인증서 유효성을 검증하는 부분을 추가해야 합니다, 이 부분은 여러분들이 직접 작성 하시는것이 좋겠죠? ^^
- 코드에서 암호한 데이터를 base64로 인코딩한 이유는 데이터의 길이를 통제하기 위해서 입니다.
- 테스트용 웹사이트 https://sample.net/test.php는 서버에 데이터를 확인 하기 위한 부분입니다.
- 불가피하게 대칭키 방식으로 키를 클라이언트에 내려서 암호화 해야 하는 상황이라면, 서버의 공캐키로 그 대칭키를 암호화해서 내려 보내는 방법이 일반적입니다.

[] 예제 코드

import ssl
import socket
import requests
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.serialization import load_pem_public_key
from cryptography import x509
from cryptography.hazmat.primitives.asymmetric import dsa, rsa
from cryptography.hazmat.primitives import hashes
import base64


hostname = 'sample.net'
port = 443

print("Enter a value to encrypt")
plain_text = input()

context = ssl.create_default_context()
with socket.create_connection((hostname, port)) as sock:
    with context.wrap_socket(sock, server_hostname=hostname) as ssock:
        # Extract the server's public key from the SSL/TLS certificate
        pem_cert = ssock.getpeercert(binary_form=True)
        cert = ssl.DER_cert_to_PEM_cert(pem_cert)
        public_key_pem = cert.split("\n")[:-1]
        public_key_pem = ''.join(public_key_pem)
  
        # Load the public key into an RSA object
        publickey = x509.load_pem_x509_certificate(public_key_pem.encode())
        publickey = publickey.public_key()
  
       # Encrypt the plain text using the server's public key
       plain_text = plain_text.encode('utf-8')
       cipher_text = publickey.encrypt(
            plain_text,
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA1(),
                label=None
            )
        )
  
       # Print the encrypted cipher text
       datas = {
        'enc' : base64.b64encode(cipher_text).decode()
        }
 
url = "https://sample.net/test.php"

#Steps for 1,2,3
print("1. input plain text : " + plain_text.decode())
print("2. python encrypt cipher_text with base64 : " + base64.b64encode(cipher_text).decode())
print("3. post body : " + str(datas))

response = requests.post(url, data= datas)
#Steps for 4,5,6
print(response.text)