우리는 패스워드를 일반 문자로 저장하면 안된다는 것을 알게 되었다. 그 대안으로 암호화(Encryption)이 있다. Encryption은 텍스트를 predictable fashion으로 마구 섞는 것이다. 암호화의 motivation은 문자열을 가져와서 알아서는 안될 사람이 그 문자열이 무엇인지 알 수 없도록 하는 것이다. Simple Substituion, Polyalphabetic Cipher는 crackable하고 컴퓨터의 도움을 받으면 꽤 쉽게 풀어낼 수 있다.
우리는 이제 현대적인 알고리즘을 사용해서 우리의 application에 암호화를 실행하고 싶다.
위의 알고리즘들은 모두 1500년대에 사용하던 암호화와 같은 원리이다. 하지만 텍스트를 더 jumble up하기 위해서 복잡도를 더 높였다. 이제는 컴퓨터도 암호를 풀기가 어려워졌다.
이러한 알고리즘들을 구현하는 현대적인 방법은 Feistel Block Chipher과 같은 block cipher에 의존한다.
원래의 일반 텍스트 블록을 가져가서 A와 B, 두 파트로 나눈다. A에서는 text를 jumble하기 위한 key를 사용하는 함수가 적용되고 이것은 원래의 나머지 텍스트 절반과 함께 합체된다. 그리고 또 다른 key를 사용한 함수를 지나서 이전 함수의 결과물과 합체된다. 이러한 과정이 계속 반복되어 결국에는 매우 풀기 힘든 텍스트 블럭이 된다.
모든 encryption algorithm에서 명심해야 할 것은 key는 비밀로 유지되어야 하며 잃어버리면 안된다는 것이다. 만약 key를 잃어버리면 해당 key를 이용하여 암호화 했던 것을 다시 풀 수 없게 된다.
User table이 유출되는 문제를 풀기 위해 암호화를 어떻게 사용할 수 있을까?
만약 일반 텍스트인 비밀번호가 저장된 사용자 테이블이 유출된다면 이 모든 테이블들은 vulnerable하다. 또한 그들의 패스워드를 이용하여 바로 엑세스가 가능해진다. 이는 많은 공격들을 가능하게 한다. 하지만 Encryption은 데이터가 유출(compromised)되었을 때 위험을 최소화 할 수 있다. 암호화된 비밀번호를 유저 테이블에 저장한다면 유저 테이블이 유출되었다고 해도 비밀번호에 걸린 암호를 풀기 위해서는 encryption key가 필요하다.
또한 plaintext password를 return하는 것보다 암호화된 패스워드를 리턴하는게 덜 나쁜 방법이다. Server에 key를 저장해서 password를 decrypt할 수 있다.
f = Fernet(key)
decrypted_pass = f.decrypt(dbuser.password)
postuser.password == decrypted_pass
하지만 key는 추가적인 위험을 불러온다. 우리는 bad datbase password, bad backup security, SQL injection에 대한 위험을 최소화 한 것일수도 있다. 그 말은, 만약 데이터베이스 자체가 유출되어도 저번 보다는 괜찮아졌다는 것이다. 하지만 아직 bad actor에 대한 문제가 있다. Key에 접근 할 수 있는 나쁜 엔지니어는 database table에 접근한 권한 또한 있으며 모든 패스워드를 간단히 decrypt할 수 있다.
유사하게 서버 자체에는 여전히 위와 같은 문제들이 남아있다.
In-flight data들도 여전히 취약하다. 이것은 비밀번호 뿐만 아니라 다른 민감한 정보들도 포함하기 때문이다. 우리는 각각의 시스템간에 메세지들이 전송되는 동안 메시지를 모두 암호화되기 위하여 Asymmectic Encryption이라고 하는 다른 Encryption을 사용할 수 있다.
Python은 암호화를 해주는 Cryptography라는 유용한 패키지를 가지고 있다.
# Import Package
from cryptography.fernet import Fernet
# Generate a Key and Instantiage a Fernet Instance
key = Fernet.generate_key()
f = Fernet(key)
# Define our message
plaintext = b"encryption is very useful!"
# Encrypt
ciphertext = f.encrypt(plaintext)
# Decrypt
decryptedtext = f.decrypt(ciphertext)