몽고DB에서 encryption하는 방법은 클라이언트 측에서 데이터를 암호화해버리는 것이다.
이를 CSFLE(client-side field level encryption)
이라고 부른다.
사용자측에서 이미 암호화하길 원하는 필드를 선택해서 암호화를 함으로써 데이터는 비암호화인체로 서버로 전송될 일이 없고 DB서버에서 비암호화 된 채로 원데이터를 직접적으로 볼 수 없게 된다.
따라서 민감한 정보를 서버에서 얻는 것도 블가능하고, DBA나 DB의 root 증명(credentials)로 디스크에서 데이터를 바로 읽어도 내용을 알 수 없게 된다.
몽고DB는 mongoDB atlas나 엔터프라이즈에서만 implicit(비명시적으로)데이터를 암호화할 수 있으므로 나는 atlas에서 무료 db를 하나 만들었다. 이곳에서 db를 생성하면 몽고 DB측에서 AWS같은 클라우드서버에 몽고DB를 자동세팅해서 DB를 사용할 수 있게 해준다. 로컬로 잠깐 봤다가 도커제공도 따로 없고..환경세팅이 젤 귀찮아서;; Free 버전도 있다. 로컬에다 설치하면 explict(명시적으로) 내가 데이터를 암호화해야 한다고 나와있다.
이 내용은 참조1의 내용을 정리한 것이다. 기본세팅은
- python 3.6+
- mongoDB Atlas 클러스터 (ver 4.2+)
- automatic field level encryption 이용
에는 엔터프라이즈 서버 패키지 설치. 위의 비명시적으로 데이터 암호화할 때는 말하는 것으로 atlas나 엔터프라이즈 이용시에 사용가능.
- 인스톨: python -m pip install "pymongo[encryption,srv]~=3.11"
- [참고1] 우클릭을 막아두어서 대략 어떻게 돌아가는지 확인하고, 아래 페이지의 코드를 다시 살펴보며 실행하는 걸 추천함.
데이터 encryption하는 방법 순서:
1. 96 bytes의 토큰 생성 (마스터키. 클라이언트측에 보관)
2. 필드를 암호화할 데이터 키(key_id) 생성( 마스터키로 암호화함. DB __keystore 컬렉션에 저장)
3. 암호화할 필드에 대한 scheme json 생성 (key_id 이용함)
4. collection(=테이블)을 JSON scheme 적용해서 생성.
결과부터 먼저:
- __keystore 컬렉션에는 data key가 암호화되어 저장되어 있다.
- keys 컬렉션에는 keys field가 암호화되어 저장되어 있다. 밑의 이미지는 keys가 서버DB에 암호화되어 보이지 않는 상태로 저장되어 있는 것을 보여줌.
import
# ref: https://www.mongodb.com/developer/quickstart/python-quickstart-fle/
# python -m pip install "pymongo[encryption,srv]~=3.11"
import datetime
import json
import os
from pathlib import Path
from pprint import pprint
from bson import json_util
from bson.codec_options import CodecOptions
from bson.binary import STANDARD
from pymongo import MongoClient
from pymongo.encryption import (Algorithm,
ClientEncryption)
from pymongo.encryption_options import AutoEncryptionOpts
from pymongo.write_concern import WriteConcern
from pymongo.errors import OperationFailure
main함수에 위의 1~4를 실행하게 하고, 마지막으로 데이터를 넣어보고 확인하는 것까지 넣었음.
if __name__ == "__main__":
main()
#
main 함수의 part1.
1. 마스터키 생성
2. 마스터키 적용해서 data key 생성 → test DB의 __keystore 컬렉션(테이블)에 저장
3. 이 data key를 이용해서 ssn이라는 field에 암호화적용한 json scheme 생성
4. 이 json scheme 로컬에 파일로 저장.
keyAltNames = data 키의 별명
이라고 생각하면 됨. 키 저장한 컬렉션(테이블)에서 저 별명으로 data key를 불러올 수 있음.
1.
def main():
password = your passwd
# atlas의 DB 접속.
MDB_URL = f"mongodb+srv://{account}:{password}@{프로젝트db이름}.lyfew.mongodb.net"
# This must be the same master key that was used to create
# the encryption key. 96바이트의 마스터키 생성
local_master_key = os.urandom(96)
# 마스터키 로컬 저장
Path("key_bytes.bin").write_bytes(local_master_key)
kms_providers = {"local": {"key": local_master_key}}
#
2.
# 2. --------------------------
# 내가 원하는 field를 암호화할 data key를 생성하고
# test DB의 __keystore 컬렉션(테이블)에 저장하자.
key_vault_namespace = "test.__keystore"
key_vault_db_name, key_vault_coll_name = key_vault_namespace.split(".", 1)
csfle_opts = AutoEncryptionOpts(
kms_providers=kms_providers,
key_vault_namespace=key_vault_namespace
)
# The MongoClient used to access the key vault (key_vault_namespace).
with MongoClient(MDB_URL, auto_encryption_opts=csfle_opts, ssl=True, ssl_cert_reqs='CERT_NONE') as client:
print("Resetting demo database & keystore ...")
client.drop_database(key_vault_db_name)
# key_vault = __keystore 테이블임
key_vault = client[key_vault_db_name][key_vault_coll_name]
# # 테이블에 인덱스 생성. keyaltname(키별명) 유니크하게 유지 설정.
# key_vault.create_index(
# "keyAltNames",
# unique=True,
# partialFilterExpression={"keyAltNames": {"$exists": True}})
# 여기가 마스터키로 데이터키 만드는 부분
client_encryption = ClientEncryption(
kms_providers,
key_vault_namespace,
client,
# The CodecOptions class used for encrypting and decrypting.
# This should be the same CodecOptions instance you have configured
# on MongoClient, Database, or Collection. We will not be calling
# encrypt() or decrypt() in this example so we can use any
# CodecOptions.
CodecOptions(uuid_representation=STANDARD))
# 위에서 만든 키 저장할 콜렉션(__keystore)에 생성한 data 키를 저장한다.
# Create a new data key and json schema for the encryptedField.
print("Creating key in MongoDB ...")
data_key_id = client_encryption.create_data_key(kms_provider='local', key_alt_names=['keys'])
# db 컬렉션에서 altname으로 데이터키 가져오기
# key_id = db.__keystore.find_one({ "keyAltNames": "example" })["_id"]
#
3.
# 3. --------------------------
print('scheme.......')
schema = {
"bsonType": "object",
"properties": {
"ssn": {
"encrypt": {
"keyId": [data_key_id],
"bsonType": "string",
"algorithm": Algorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random
}
}
}
}
#
4.
# 4. -----------------------
json_schema = json_util.dumps(schema,
json_options=json_util.CANONICAL_JSON_OPTIONS,
indent=2)
Path("json_schema.json").write_text(json_schema)
# 키랑 스키마 다시 받아오는 건데 참조용으로 달아둠
# Load the master key from 'key_bytes.bin':
# key_bin = Path("key_bytes.bin").read_bytes()
# Load the 'person' schema from "json_schema.json":
collection_schema = json_util.loads(Path("json_schema.json").read_text())
#
main 함수의 part2.
1. test db에서 people이라는 컬렉션에 접속해서 이전 데이터를 날림 → 앞서 만든 json scheme을 validator로 적용한 컬렉션을 새로 만들기
2. 데이터넣고 확인.
MongoClient 생성시에 파라미터로 auto_encryption_opts = csfle_opts
를 해주면 find할 때 알아서 데이터를 복호화해서 보여준다. 옵션을 안 넣으면 암호화한 상태로 보여줌.
# auto_encryption_opts=csfle_opts 이게 없으면 find_one할 때 암호화된 상태로 보여줌
with MongoClient(MDB_URL, auto_encryption_opts=csfle_opts, ssl=True, ssl_cert_reqs='CERT_NONE') as client:
db_namespace = 'test.people'
db_name, coll_name = db_namespace.split(".", 1)
db = client[db_name]
# Clear old data
db.drop_collection(coll_name)
# Create the collection with the encryption JSON Schema.
db.create_collection(
coll_name,
# uuid_representation=STANDARD is required to ensure that any
# UUIDs in the $jsonSchema document are encoded to BSON Binary
# with the standard UUID subtype 4. This is only needed when
# running the "create" collection command with an encryption
# JSON Schema.
codec_options=CodecOptions(uuid_representation=STANDARD),
write_concern=WriteConcern(w="majority"),
# 암호화하는 field에 대한 validator
validator={"$jsonSchema": collection_schema})
coll = client[db_name][coll_name]
tmp_data= {
"full_name": "Sophia Duleep Singh",
"ssn": "123-12-1234",
}
id_ = coll.insert_one(tmp_data).inserted_id
print('id_:', id_)
pprint(coll.find_one())
에러해결
1. RuntimeWarning: Failed to start mongocryptd: is it on your $PATH?
이건 또 뭔 에러인가 싶었는데, 찾아보니 automatic field level encryption 이용
할 때 mongocryptd
가 필요하고, 이 파일은 MongoDB Enterprise Server package
에 포함되어 있다. 그 말인 즉슨 엔터프라이즈 서버 패키지를 설치해야한다는 것.
- 다운로드 페이지: https://www.mongodb.com/try/download/enterprise?tck=docs_server
mongocryptd is required for automatic field level encryption and is included as a component in the MongoDB Enterprise Server package, or separately as the mongodb-enterprise-cryptd package.
- 참조: https://docs.mongodb.com/manual/reference/security-client-side-encryption-appendix/
설치하고 뭘 해야한다? mongocryptd
를 사용하기 위해 PATH에 mongocryptd
가 들어가 있는 폴더를 추가해줘야 한다. 나는 pycharm 사용하는 os가 윈도우10이고, atlas의 몽고DB 버전은 4.4.8로 엔터프라이즈 서버 패키지도 4.4.8로 다운받고, 설치한 후 Path에 C:\Program Files\MongoDB\Server\4.4\bin
추가해줌.
윈도우10 환경설정은 어찌하는줄 알쥬? 내 PC>속성>고오급 시스템 설정>환경 변수>Path에 추가
바로 적용 안 되면(안 될거임..) 재부팅ㄱㄱ
참조:
- https://docs.mongodb.com/manual/reference/security-client-side-encryption-appendix/
2. getpass()함수는 파이참에서 실행이 안 됨.
- 좀 딴 소리이지만, 해보다 알았음. 명령 프롬프트에선 된다고 함. 끗.
참조
- 가장 많이 참고함: https://www.mongodb.com/developer/quickstart/python-quickstart-fle/
- https://www.mongodb.com/developer/quickstart/python-quickstart-fle/
- 기본사용법: https://www.mongodb.com/developer/quickstart/python-quickstart-crud/
- pymongo 공식 doc: https://pymongo.readthedocs.io/en/stable/installation.html
- 엔터프라이즈 서버 패키지 다운로드 페이지: https://www.mongodb.com/try/download/enterprise?tck=docs_server