[Project를 위한 공부]

[Project 를 위한 공부] API 와 같은 중요한 정보 관리 방법들..

dyk98 2025. 1. 23. 18:12

우리가 서비스를 만들때 특히나 OpenAI 의 API나 다른 사이트들이 제공하는 API 를 활용할때가 있다. 이 API 는 암호화 되거나 보안이 적용이 되어야 하는데 과연 어떤식으로 관리를 할까? 에 대한 의문이 들었다. 지금 현제 그냥 프로젝트를 만들때, .env 파일에 적어 놓는 것은 아무리 생각해도 나중에 보안성에서 떨어진다고 생각 되기 때문이다. 실제 그럼 어떻게 관리가 되가고 있을까? 에 대해 생각 했을때 API 키를 해시화(복호화가 불가능하게)시키거나 복호화가 가능하도록 암호화 시킨다는 방법이 있다고 한다.

1. 해시화(Hashing)(복호화가 불가능한경우)

  • Hashing, 즉 해시화는 데이터를 단방향으로 암호화하는 기법으로, 입력값을 고정된 길이의 출력값으로 변환이된다.
    • 대표적으로 OPENAI나 Antropic API 키를 발급받을때 사용되는듯하다 발급시 따로 저장 안해두면 세로 다시 발급을 받아야되기 때문...
  • 일반적으로 SHA-256, bcrypt, argon2와 같은 해시 알고리즘을 사용함. (내가 알기론 이 해시알고리즘에 따라 해시키의 길이등 커버 정도가 다른걸로 알고 있음)
  • 해시된 갑슨 복호화가 불가하며, 입력값과 출력값간의 관계를 복원할수 없음. 이것이 장점이자 단점인듯하다....

그럼 이런 해시화를 했을때의 장단점을 살펴보자
우선 대표적인 장점들을 정리해보자.

  1. 해시화를 했을시 보안성이 높다
  • 해시값은 원래 데이터를 복구할 수 없기 때문에 데이터 유출 시에도 원래 API 키를 알 수 없음.
    • 예시론, DB 가 유출이 되더라고 해시값만 저장이 되어 있다면 원래 API키는 안전하다는것.
    • 내가 알기론 비트코인도 어찌보면 하나의 해시키로 인걸로 알고 있는데 그건 좀더 알아봐야할듯?
  1. 입력 데이터의 무결성 확인 가능
  • 해시값은 입력값의 유일한 서명처럼 작도암. 즉 데이터가 변경되었는지 확인하는데 유용함.
    • 예시론, 사용자가 API키를 입력했을때, 원래 저장된 해시값과 비교하여 유효성을 확인할수 있음 => 그래서 아마 LLM회사들의 API Key를 그런식으로 발급하는듯...?
  1. 해시값의 크기가 고정됨
  • 입력값의 길이에 상관없이 해시값의 크기는 일정함.
  • 예: SHA-256은 항상 32바이트 길이의 해시값을 변환함.
  1. 간단하게 구현이 가능
  • 해시화는 암호화보다 구현이 쉽고 성능또한 우수함. 추가적으로 암호화 키를 관리할 필요없음.

하지만 Hashing을 했을 때의 단점또한 있는데,

  1. 복호화가 불가능함, 즉 decrypt 가 불가 함.
  • 해시화는 단방향 함수이기에, 원래의 API키를 복구할수 없다.
    • 예시로 사용자가 자신의 API 키를 잊어 버리면 새로 발급... 그래서 antropic 도 다시 발급받아야하는거였군...
  1. 데이터 검증이 어려움
  • 복호화를 할수 없기에, 사용자의 API키를 검증하려면 원래의 API 키와 입력된 키으니 동일한 방식으로 해시화한후 비교해야함..
    • 예: "입력값 == 해시값" 방식으로는 확인할 수 없습니다. 반드시 같은 해시 알고리즘을 다시 적용해야한다는거 ...
  1. 충돌의 가능성이 있음.
  • 이론적으로 해시 충돌(두 개의 다른 입력값이 동일한 해시값을 갖는 현상)이 발생할수 있음. 물론 현대적인 해시 알고리즘에서는 매우 희박함.

그럼 API키를 해시화하여 저장해야하는 경우를 정리해보자

  1. 우선 사용자가 API키를 인증할때, 즉 사용자가 자신의 API키를 제공하고, 서버는 이를 해시화하여 데이터베이스에 저장된 해시값과 비교함.
    • 예시로
      • 사용자가 제공한 키: my-secret-key
      • 저장된 값: SHA256(my-secret-key)
        뭔가 내가 Azure TTS 쓸때 느낌남...
        그런데 이런 방식을 쓰면 사용자의 실제 API키를 서버에 저장하지 않으므로 데이터 유출 시에도 안전함.
  2. 데이터베이스 보안이 중요한경우
    • 데이터베이스가 침해되더라도 해시화된 값은 복호화할수 없기에 민감한 정보가 보호됨.
    • 예: 해시화되 API키는 비밀번호와 비슷하데 다룰수 있음.
  3. API키를 발급하고 재발급하는 시스템
    • 사용자가 API키를 잊어버렸을때, 새로운 키를 발급하여 다시 저장하는 방식에 적합.
    • 해시화된 키는 다시 복원할 필요가 없으므로, 시스템을 단순화할수 있음.

그렇다면 API키를 해시화하는것에 적합하지 않을 경우는?

  1. 복호화(Decrption)가 필요한경우
  • 해시화는 단방향이기 때문에, 원래의 API 키를 다시 복원해야하는 경우에는 적합하지 않음
    • 이 경우 AES 와 같은 대칭 암호화를 사용하여 키를 암호화 한다고 한다.
  1. 시스템간의 키 공유가 필요한경우
  • 다른 서비스나 API 서버와 키를 공유해야한다며, 해시화된 키는 적합하지 않음.
    • 이 경우 키를 암호화한후 안전한 방식 (예를 들어 HTTPS) 를 통해 전달한다고 한다.

즉 해시화를 해야된다고 생각했을떄는,

  • 데이터 복호화가 필요 없을경우

  • 민감한 정보를 안전하게 저장할떄

  • 데이터 무결성을 확인할떄
    그리고 해시화를 하지 말야할 경우는,

  • 데이터르 복원해야 할떄
    라고 생각된다.

  • 근데 양자 컴퓨터 생기면 이것도 다 소용없을 수도 있다. 기본적으로 hashing은 일반적으로 여러가지의 hasing 알고리즘을 사용하여 세로 값을 만드는건데, 이또한 일정한 페턴이나 해석이 사람이나 현제 컴퓨터로 하기 매우 어려울뿐 해석이 가능할수도 있기 때문이다. 양자 컴퓨터는 그것이 가능할정도의 수학적 인 능력을 가진 컴퓨터이기 때문이다. 그래서 아마 양자 컴퓨터가 생기면 더이상 비트코인과 같은 가상화폐가 한낱 데이터 쪼가리로 변화할수 있다는 소리를 하는듯 하다. 뭐 여튼 지금은 거의 불가라고 생각 되기에 보안적인 면에서 좋은듯하다.

2. AES & RSA (복호화가 가능한경우)

복호화(Decryption)암호화(Encryption)된 데이터를 원래의 형태로 되돌리는 과정인데, 해시화는 복호화가 불가능한 단방향 방식이므로, 복호화가 필요한 경우에는 대칭 암호화(AES)나 비대칭 암호화 방식(RSA)을 사용해야 한다.

1. 복호화가 필요한 경우

  • 복호화를 사용하는 대표적인 상황은 다음과 같습니다:
    • API 키 관리:
      • 서버가 사용자의 API 키를 복호화하여 다른 서비스로 전달하거나 사용해야 할 때.
    • 데이터 보호 및 복구:
      • 민감한 정보를 안전하게 저장하지만, 필요 시 데이터를 복구해야 하는 경우.
    • 시스템 간 데이터 전송:
      • 두 시스템이 데이터를 안전하게 공유하기 위해 암호화 및 복호화 과정을 사용하는 경우.

2. AES (대칭 암호화) 방식

  • AES 복호화 과정

    • 대칭 암호화는 데이터를 암호화할 때와 복호화할 때 동일한 비밀 키(secret key)를 사용한다.
    • Cryptography 와 같은 라이브러리로 AES 복호화 를 할수 있다.

    예시

    from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
    from cryptography.hazmat.primitives import padding
    from cryptography.hazmat.backends import default_backend
    import os
    
    # AES 암호화 함수
    def encrypt_data(data: str, secret_key: bytes) -> bytes:
        # 데이터 패딩 (AES는 고정 블록 크기 16바이트 사용)
        padder = padding.PKCS7(128).padder()
        padded_data = padder.update(data.encode()) + padder.finalize()
    
        # 초기화 벡터(IV) 생성
        iv = os.urandom(16)
    
        # AES 암호화
        cipher = Cipher(algorithms.AES(secret_key), modes.CBC(iv), backend=default_backend())
        encryptor = cipher.encryptor()
        encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
    
        # IV와 암호화된 데이터 결합하여 반환
        return iv + encrypted_data
    
    # AES 복호화 함수
    def decrypt_data(encrypted_data: bytes, secret_key: bytes) -> str:
        # IV와 암호화된 데이터 분리
        iv = encrypted_data[:16]
        encrypted_message = encrypted_data[16:]
    
        # AES 복호화
        cipher = Cipher(algorithms.AES(secret_key), modes.CBC(iv), backend=default_backend())
        decryptor = cipher.decryptor()
        padded_data = decryptor.update(encrypted_message) + decryptor.finalize()
    
        # 패딩 제거
        unpadder = padding.PKCS7(128).unpadder()
        data = unpadder.update(padded_data) + unpadder.finalize()
    
        return data.decode()
    
    # 비밀 키 생성 (32바이트 = 256비트 키)
    secret_key = os.urandom(32)
    
    # 예제 데이터
    original_data = "my_secret_api_key"
    
    # 암호화
    encrypted = encrypt_data(original_data, secret_key)
    print("Encrypted Data:", encrypted)
    
    # 복호화
    decrypted = decrypt_data(encrypted, secret_key)
    print("Decrypted Data:", decrypted)

이러한 AES 사용시 고려해야할것들이 있는데,

  1. 대칭 키 보관:
    • 암호화와 복호화 모두 동일한 키를 사용하기 때문에 키를 안전하게 저장해야함.
    • 키가 유출되면 데이터가 노출될 위험이 있음.
    • 키 관리 시스템(예: AWS Secrets Manager, HashiCorp Vault)을 사용하는 것이 권장함.
  2. IV (Initialization Vector):
    • 암호화 시 사용하는 IV(초기화 벡터)는 암호화의 무작위성을 추가하는 데 필요하며, IV는 암호화 데이터와 함께 저장하거나 전송한다는 점.

3. RSA(비대칭 암호화) 방식

  • RSA는 비대칭 암호화 알고리즘으로, 두 개의 키를 사용합니다:

    • 공개 키 (Public Key): 데이터를 암호화하는 데 사용.

    • 개인 키 (Private Key): 데이터를 복호화하는 데 사용.
      그래서 키가 그렇게 만들었군... AWS E2C 보안그룹 띄울때 RSA 화 했던걸로 기억하는데...
      또한 이런 RSA는 cryptography 라이브러리를 통해도 암호화 복호화가 가능하다
      예시로:

      from cryptography.hazmat.primitives.asymmetric import rsa
      from cryptography.hazmat.primitives.asymmetric import padding
      from cryptography.hazmat.primitives import hashes
      
      # RSA 키 페어 생성
      private_key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=2048
      )
      public_key = private_key.public_key()
      
      # 암호화 함수
      def encrypt_data_with_rsa(data: str, public_key) -> bytes:
        encrypted = public_key.encrypt(
            data.encode(),
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA256(),
                label=None
            )
        )
        return encrypted
      
      # 복호화 함수
      def decrypt_data_with_rsa(encrypted_data: bytes, private_key) -> str:
        decrypted = private_key.decrypt(
            encrypted_data,
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA256(),
                label=None
            )
        )
        return decrypted.decode()
      
      # 예제 데이터
      data = "my_secret_api_key"
      
      # 암호화
      encrypted_data = encrypt_data_with_rsa(data, public_key)
      print("Encrypted Data:", encrypted_data)
      
      # 복호화
      decrypted_data = decrypt_data_with_rsa(encrypted_data, private_key)
      print("Decrypted Data:", decrypted_data)
      

RSA 사용 시 고려사항들이 있는데

  • RSA는 비대칭 키 관리가 필요합니다.
    • 공개 키는 안전하지 않은 환경에서도 사용할 수 있지만, 개인 키는 안전한 곳에 저장해야 한다는점.
  • 데이터 크기 제한:
    • RSA는 큰 데이터를 처리하는 데 적합하지 않지만 대신, 대칭 암호화(AES) 키를 암호화하는 데 자주 사용된다고한다.

그러니 AES(대칭 암호화) 경우, 데이터를 암호화 하여 저장하거나, 데이터를 전송할떄 사용되며, API키 나 대량 데이터를 암호화 할때 적합한듯하다 또한 RSA(비대칭 암호화) 경우 공개 키로 데이터를 암호화하고, 개인키로 복호화해야하는경우, 주로 AES 키를 이중으로 암호화하여 하는데 사용(이런 방식을 하이브리드라고 한다).

그러니 결론은 복호화가 필요한 경우 AES와 RSA 중 하나를 선택하면 되며,

  • 단순히 데이터를 암호화/복호화할 경우: AES
  • 다른 사용자와 키를 안전하게 교환해야 할 경우: RSA
  • 강력한 보안과 성능을 모두 원할 경우: AES + RSA (하이브리드)
    라고 생각 하면 될듯하다.