암호화 알고리즘은 결국 공개된 표준이기 때문에 해당 표준의 상세 여부를 따지기보다 암호화 열쇠를 어떻게 만들고 저장하고 관리할 것인지가 실제 가장 중요하다. 한국인터넷진흥원(KISA)에서는 암호화된 데이터와 암호화 키는 물리적으로 안전하게 분리된 장소에 보관해야 한다고 권고하고 있다. 데이터는 암호화하여 DB에 보관한다고 하면 키는 어디에 어떻게 보관해야 할까?

일반적으로 암호화 키, 인증서 등 민감한 데이터를 안전하게 보호하는 서비스가 볼트(Vault)이다. 볼트 서비스는 민감 데이터를 DB에 전달하기 전에 암호화를 하며, DB에 저장된 암호화된 민감 데이터는 Vault 없이는 해독할 수 없게 한다. 이러한 과정을 통해 민감 데이터를 안전하게 보호한다. 본 글에서는 해시코프(HashiCorp)의 크로스플랫폼 패스워드 및 인증 관리 시스템인 볼트서비스와 루니버스 볼트 서비스를 통해 안전한 키 및 데이터 관리 방법에 대해 살펴본다.
해시코프의 볼트(Vault) 서비스
사용 사례
Secrets Management
조직의 도메인에 대한 SSL 인증서 및 키, 회사 데이터베이스 서버에 연결하기 위한 자격 증명 등을 안전한 방식으로 저장할 수 있다.

Static Secrets: Key/Value Secrets Engine 예제
- request payload에 API key 저장
$ tee payload.json <<EOF { "key": "AAaaBBccDDeeOTXzSMT1234BB_Z8JzG7JkSVxI" } EOF
- secret path 설정과 함께 key를 vault로 전송
$ curl --header "X-Vault-Token: <client_token>" \ --request POST \ --data @payload.json \ http://127.0.0.1:8200/v1/kv-v1/eng/apikey/Google
- 잘 저장되었는지 secret path로 확인
$ curl --header "X-Vault-Token: <client_token>" \ http://127.0.0.1:8200/v1/kv-v1/eng/apikey/Google | jq // 리턴된 json { "request_id": "dc0f0baf-16cb-af3f-db11-2c986df02e60", "lease_id": "", "renewable": false, "lease_duration": 2764800, "data": { "key": "AAaaBBccDDeeOTXzSMT1234BB_Z8JzG7JkSVxI" }, "wrap_info": null, "warnings": null, "auth": null }
- 하지만 위와 같이 secret engine으로 K/V를 사용하면 key 값이 평문으로 암호화 없이 저장되어 문제가 될 수 있다.
Data Encryption
- 예제 1

- 문자열 “4111 1111 1111 1111” 을 암/복호화 해보자.
// base64 인코딩 방법 const fs = require('fs'); let buff = '4111 1111 1111 1111'; let base64data = buff.toString('base64'); or $ base64 <<< "4111 1111 1111 1111" NDExMSAxMTExIDExMTEgMTExMQo= // Root token 또는 권한 있는 토큰과 함께 encryption API 호출 $ curl --header "X-Vault-Token: <client_token>" \ --request POST \ --data '{"plaintext": "NDExMSAxMTExIDExMTEgMTExMQo="}' \ http://127.0.0.1:8200/v1/transit/encrypt/orders | jq // 리턴된 데이터 { "request_id": "f92362bd-8f06-d847-7275-5c2124003479", "lease_id": "", "renewable": false, "lease_duration": 0, "data": { "ciphertext": "vault:v1:haARDbBl/dry0pNSpztCHZ8K+ZKFZyn6z7z2cRfESZzfWdPD+GoSYxwJaRuiBtwC", "key_version": 1 }, "wrap_info": null, "warnings": null, "auth": null } // Root token 또는 권한 있는 토큰과 함께 decryption API 호출 $ curl --header "X-Vault-Token: " \ --request POST \ --data '{"ciphertext": "vault:v1:haARDbBl/dry0pNSpztCHZ8K+ZKFZyn6z7z2cRfESZzfWdPD+GoSYxwJaRuiBtwC"}' \ http://127.0.0.1:8200/v1/transit/decrypt/orders> | jq // 리턴된 데이터 { "request_id": "57d8b84f-4e81-42c6-3829-ecb8241072bb", "lease_id": "", "renewable": false, "lease_duration": 0, "data": { "plaintext": "NDExMSAxMTExIDExMTEgMTExMQo=" }, "wrap_info": null, "warnings": null, "auth": null } // base64 decode $ base64 --decode <<< "NDExMSAxMTExIDExMTEgMTExMQo=" 4111 1111 1111 1111
해커가 DB 액세스 계정을 탈취하여 접근하여도 암호화된 데이터만 획득하기 때문에 안전하게 데이터를 지킬 수 있다.
- 예제 2


High-Level Overview

- 인증을 통해 policy와 token을 리턴받아 사용한다.

Barrier 영역
Barrier는 장벽이라는 의미 그대로 데이터의 안전한 사용을 위한 강철벽이라고 생각하면 된다. 스토리지 백엔드 간에 흐르는 모든 데이터는 Barrier를 통과한다. Barrier는 암호화된 데이터만을 기록하며, 데이터가 필요한 시점에 복호화(암호해독)가 되도록 한다.
Barrier는 시작하면 “sealed” 상태가 되고 모든 데이터를 보호하는데 사용되는 암호화 키가 생성된다.
- 암호화 키는 마스터 키로 보호된다.
- 기본적으로 Sharmir’s secret sharing algorithm을 사용하여 마스터 키를 5개로 분할하여 공유되고, 마스터 키를 재구성 하는데 이 중 3개가 필요하다.
Barrier가 “unsealed” 되어야만 내부 데이터에 접근 할 수 있다.
Core
Secret Engine
데이터를 저장, 생성, 암호화하는 구성요소 이다. “kv(key/value) secret engine” 같은 간단한 secret engine들은 쿼리할 때 단순하게 key&value 형식으로 데이터를 저장하고 읽을 수 있으며, 좀 더 복잡한 secret engine들은 서비스에 연결하고 요청을 하는 시점에서 동적으로 자격증명을 생성할 수도 있다.
- 다양한 secret engine 목록 : Active Directory, AliCloud, AWS, Azure, Consul, Cubbyhole, Database, Google Cloud, Google Cloud KMS, KMIP, Key/Value, Identity, Nomad, PKI, RabbitMQ, SSH, TOTP, Transit
Policy
정책이란 vault에 로그인한 유저가 어떤 기능을 사용할 수 있느냐를 정의하는 것이다. 정책은 hcl 문법으로 작성한다.
- 작성 예
# /my-policy.hcl
# Normal servers have version 1 of KV mounted by default, so will need these
# paths:
path "secret/*" {
capabilities = ["create"]
}
path "secret/foo" {
capabilities = ["read"]
}
# Dev servers have version 2 of KV mounted by default, so will need these
# paths:
path "secret/data/*" {
capabilities = ["create"]
}
path "secret/data/foo" {
capabilities = ["read"]
}
Storage Backend
Auth & Secret engine
Auth 모듈과 Secret engine은 원하는 대로 선택하여 구성할 수 있다.
- Vault v1.6.2에 기본 등록된 auth/secret 플러그인 목록
"data": { "auth": [ "alicloud", "app-id", "approle", "aws", "azure", "centrify", "cert", "cf", "gcp", "github", "jwt", "kerberos", "kubernetes", "ldap", "oci", "oidc", "okta", "pcf", "radius", "userpass" ], "database": [ "cassandra-database-plugin", "couchbase-database-plugin", "elasticsearch-database-plugin", "hana-database-plugin", "influxdb-database-plugin", "mongodb-database-plugin", "mongodbatlas-database-plugin", "mssql-database-plugin", "mysql-aurora-database-plugin", "mysql-database-plugin", "mysql-legacy-database-plugin", "mysql-rds-database-plugin", "postgresql-database-plugin", "redshift-database-plugin" ], "secret": [ "ad", "alicloud", "aws", "azure", "cassandra", "consul", "gcp", "gcpkms", "kv", "mongodb", "mongodbatlas", "mssql", "mysql", "nomad", "openldap", "pki", "postgresql", "rabbitmq", "ssh", "totp", "transit" ] },
Vault auth/secret engine 설정 방법
1. auth 설정 방법
- $ vault auth enable -path=
- GitHub 인증 예
- GitHub 설정
- organization 구성
- https://github.com/settings/organizations
- 시나리오를 위해 vault-test라고 생성했다고 가정한다.
- Personal access tokens 등록
- https://github.com/settings/tokens
- 토큰 scopes 설정 시 user 또는 read:org 권한을 넣어야 한다.
- organization 구성
- GitHub 인증 활성화
$ vault auth enable -path=github github Success! Enabled github auth method at: github/
- GitHub 설정
- GitHub 인증 설정
- 인증 범위는 github에서 구성한 organization(justin-vault-test) 내의 유저로 설정한다.
- 그 다음 명령어는 조직의 모든 사용자에게 default와 test-policy 정책을 매핑하게 한다.
$ vault write auth/github/config organization=justin-vault-test Success! Data written to: auth/github/config $ vault write auth/github/map/teams/my-team value=default,test-policy Success! Data written to: auth/github/map/teams/my-team
- GitHub 인증 설정
- GitHub 인증을 통한 토큰 얻기
- organization 구성 시 생성된 GitHub 토큰을 이용해서 로그인한다.
- GitHub 인증을 통한 토큰 얻기

// 위 github token 을 복사하여 사용
$ vault login -method=github token=0c9c83b96e1c3b8f8f0997801f27e8fa85cb58ab
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.
Key Value
--- -----
token s.jjZzLQMJprfYstAmc7DoQvzT
token_accessor NL5ua6DUE6E2AfSsPb37sJLH
token_duration 768h
token_renewable true
token_policies ["default"]
identity_policies []
policies ["default"]
token_meta_org justin-vault-test
token_meta_username SungMin-Ha
2. secret engine 설정 방법
- $ vault secrets enable -path=
- AWS secret engine 활성화 예
$ vault secrets enable -path=aws aws Success! Enabled the aws secrets engine at: aws/
Vault server 맛보기
CLI
$ vault operator init
Unseal Key 1: 6VoUsGk140OnDi2Z3OcXBJyr/cS3Zv+gnAGDOteiOeFO
Unseal Key 2: 4wrk+3AX7Z39BKSZcAsGNO+5Zsn1s4MjWK/2N5o0Tc7U
Unseal Key 3: HIqhJ9p5rRaYjN8Rjhm6bDMO+zVlyy3Tc2HD+0MeY6dJ
Unseal Key 4: hkmXS/d6Wdmg4xkyJVLlWJP8L8uEKhmKbOH0nOm1PNfE
Unseal Key 5: /GJp/G+7aoMjTqarPqmxYtN+oSEw38C4kWZNQNuw6zsf
Initial Root Token: s.eRWvoMPfKdxk2ja7kqIlSuR6
Vault initialized with 5 key shares and a key threshold of 3. Please securely
distribute the key shares printed above. When the Vault is re-sealed,
restarted, or stopped, you must supply at least 3 of these keys to unseal it
before it can start servicing requests.
Vault does not store the generated master key. Without at least 3 key to
reconstruct the master key, Vault will remain permanently sealed!
It is possible to generate new unseal keys, provided you have a quorum of
existing unseal keys shares. See "vault operator rekey" for more information.
실행됨과 동시에 Unseal Key 5개와 Root token이 생성된다.
- unseal을 하려면 임계값 개수(최소)의 unseal key 3개를 소유하고 있어야 한다.
- unseal api를 세번 호출하여 성공해야 unseal이 된다.
- Unseal 예
$ vault operator unseal 6VoUsGk140OnDi2Z3OcXBJyr/cS3Zv+gnAGDOteiOeFO $ vault status Key Value --- ----- Seal Type shamir Initialized true Sealed true # <-- true 로 해제되지 않음을 확인 가능 Total Shares 5 Threshold 3 Unseal Progress 1/5 Unseal Nonce n/a Version 1.6.2 Storage Type inmem HA Enabled false $ vault operator unseal hkmXS/d6Wdmg4xkyJVLlWJP8L8uEKhmKbOH0nOm1PNfE $ vault operator unseal 4wrk+3AX7Z39BKSZcAsGNO+5Zsn1s4MjWK/2N5o0Tc7U $ vault status Key Value --- ----- Seal Type shamir Initialized true Sealed false # <-- false 로 해제됨을 확인 가능 Total Shares 5 Threshold 3 Version 1.6.2 Cluster Name vault-cluster-6075fd5f Cluster ID c08caafe-09e6-c546-3216-b30a5d289ece HA Enabled falseVault에는 기본 해지 메커니즘이 있기 때문에 동적 비밀은 사용 후 즉시 해지 될 수 있으므로 비밀이 존재하는 시간을 최소화 할 수 있습니다.
- Unseal 예
- Vault API 사용 시 필요 (계정 생성 등)
- 예 : curl –header “X-Vault-Token: s.eRWvoMPfKdxk2ja7kqIlSuR6”
Node.js 에서 vault client 사용 예
- node-vault
- install 및 vault init & unseal
// install // make sure to use node.js version >= 6 $ npm install node-vault // init and unseal var options = { apiVersion: 'v1', // default endpoint: 'http://127.0.0.1:8200', // default token: '1234' // optional client token; can be fetched after valid initialization of the server }; // get new instance of the client var vault = require("node-vault")(options); // init vault server vault.init({ secret_shares: 1, secret_threshold: 1 }) .then( (result) => { var keys = result.keys; // set token for all following requests vault.token = result.root_token; // unseal vault server return vault.unseal({ secret_shares: 1, key: keys[0] }) }) .catch(console.error);
- install 및 vault init & unseal
Vault 의 Master Key 관리 방식

Shamir Secret Sharing을 사용한다.
- 비밀(정보)을 여러 조각으로 쪼개서 일정 개수 이상의 조각이 모였을 때만 비밀(정보)을 다시 복원할 수 있도록 하는 방법이다.
- 하나의 secret을 여러 조각으로 나누어 분산시켜 저장한다. (shares)
- 원래의 secret을 복원하기 위해서는 반드시 일정한 수 이상의 share가 필요하다. (threshold)
- Vault는 초기화 단계에 5개의 키가 생성되고 이 중 최소 3개가 있어야 Master Key로 복원된다. 때문에 DB나 Vault 서버가 통째로 털려도 이 키 3개가 없다면 공격자는 정보를 볼 수 없게 된다.
그렇다면 Shamir Secret Sharing 방식은 완벽하게 안전할까?
- 완벽하지 않다! 그렇기에 보완하기 위한 알고리즘들이 계속 등장하고 있다.
Shamir’s secret sharing의 특징은 모든 구성원이 나눠갖는 비밀이 서로 유일(unique)하며 평등하다는 것입니다. 또한 threshold 이상의 조각만 있으면 secret를 복구할 수 있기 때문에 일부 비밀 조각이 유실되더라도 안전하고, 소수의 배신자가 있더라도 secret을 복구할 수 없습니다. 이런 특징들은 한계점이 되기도 합니다. 모든 shared secret이 동등하기 때문에 비밀을 나눌 때 보안 등급에 따라 나누기가 힘들고, 비밀 조각을 제공할 때 일부러 틀린 비밀 값을 주는 것을 방지할 수 없으며 비밀을 복구할 때도 누군가 오염된 값을 줘서 비밀이 제대로 복구되었는지 알 수 없습니다. Shamir’s secret sharing의 단점들은 이후에 나온 여러 연구들에 의해 보강 되었습니다. 1987년 Feldman과 Benaloh는 shared secret 소유지들이 자신의 share를 검증할 수 있는 verifiable secret sharing scheme을 각각 만들었습니다. 1996년 Stadler와 1999년 Schoenmakers, 2004년에는 Pederson이 자신의 share 뿐 아니라 다른 사람의 share도 검증 가능한 publicly verifiable secret sharing 알고리즘을 각각 만들었습니다.
Dynamic Secrets
key/value secret도 지원하지만, dynamic secrets (동적 비밀정보) 방식도 지원한다. (각 secret engine에서 생성)
- dynamic secrets는 access 시점에 secret key가 생성된다. 즉, access 전까지는 secret key가 존재하지 않는다는 말이다. 때문에 서로 다른 클라이언트 간에 secret key 탈취에 대한 염려가 없다.
- Vault에는 기본 해지 메커니즘이 있기 때문에 동적 비밀은 사용 후 즉시 해지 될 수 있으므로 비밀이 존재하는 시간을 최소화 할 수 있다.
- 보통 secret 정보를 app 자체가 갖고 있거나 외부 저장소로부터 획득하여 사용했다면, vault를 사용할 경우 secret 정보를 동적으로 생성하고 app은 이를 사용하다가 필요가 없을 경우 폐기 또는 vault에 의해서 스스로 삭제될 수 있어 더 안전하다고 할 수 있다.
- app은 secret 정보가 필요할 때마다 vault를 이용하면 된다.
- dynamic secret 생성 예 (AWS secrets engine 사용)
- AWS에 루트 사용자로 가입하고 로그인 한다.
- access key와 secret key를 생성한다.
- 우측 상단 본인 아이디 메뉴를 열고 내 보안 자격 증명 선택 → 액세스 키 항목에서 만들 수 있다.
- secret engine을 aws로 설정하고 access key와 secret key를 Vault에 등록한다.
$ vault secrets enable -path=aws aws Success! Enabled the aws secrets engine at: aws/ $ vault write aws/config/root \ > access_key=$AWS_ACCESS_KEY_ID \ > secret_key=$AWS_SECRET_ACCESS_KEY \ > region=us-east-1 Success! Data written to: aws/config/root
4. AWS API를 통해 IAM user를 생성할 때 적용할 정책을 role에 매핑한다.
- “my-role” 에 대한 자격 증명을 요청하면 자격 증명을 생성하고 IAM 정책을 연결하도록 설정한다.
$ vault write aws/roles/my-role \ credential_type=iam_user \ policy_document=-<<EOF { "Version": "2012-10-17", "Statement": [ { "Sid": "Stmt1426528957000", "Effect": "Allow", "Action": [ "ec2:*" ], "Resource": [ "*" ] } ] } EOF
5. 자격 증명을 요청해 보면 요청 할 때마다 새로운 IAM user와 access key, secret key가 생성 되는 것을 확인할 수 있다.
- Vault에 의해 768시간 후 자동으로 삭제된다.
$ vault read aws/creds/my-role Key Value --- ----- lease_id aws/creds/my-role/jIYSHn55DdCTLj4b4N9nXu1B lease_duration 768h lease_renewable true access_key AKIAU6VQQXFVDHZTHE4S secret_key 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 security_token <nil> $ vault read aws/creds/my-role Key Value --- ----- lease_id aws/creds/my-role/QupnHVvlej2lbj3Zf0J4zAVv lease_duration 768h lease_renewable true access_key AKIAU6VQQXFVGNFZ42EM secret_key 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 security_token <nil> $ vault read aws/creds/my-role Key Value --- ----- lease_id aws/creds/my-role/Hl8DaUkhud9mNHw5RtEeuKFn lease_duration 768h lease_renewable true access_key AKIAU6VQQXFVJWFKOEVO secret_key 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 security_token <nil>
- Vault에 의해 768시간 후 자동으로 삭제된다.
- 실제로 AWS 사용자 탭에 가보면 새로운 사용자들이 추가되어 있는 것을 볼 수 있다.

마치는 말
지금까지 민감 데이터의 관리 방법중 한 가지인 볼트 서비스에 대해 간략히 알아보았다. 실제 해시코프의 볼트(Vault) 서비스는 위에 언급한 내용 외에도 많은 기능을 제공하고 있다. 현재 루니버스에서도 Vault를 제공하고 있다. 다음 글에서는 루니버스의 볼트 서비스에 대해 살펴본다.