1부 HashiCorp Vault에 이어서 이번에는 루니버스에서 제공하는 볼트 서비스(Vault Service)에 대해 살펴본다.
루니버스의 볼트 서비스
HashiCorp Vault가 안전한 키 관리를 위해 다양한 인증 모듈과 secret engine을 지원하고, 정책을 통한 유저 권한 설정 및 관리를 지원한다면 루니버스 볼트 서비스는 단 한 가지 방식으로 키 관리를 서비스한다.
루니버스 볼트 서비스는 키스토어(Keystore) 파일, 즉 Private Key를 비밀번호로 암호화한 텍스트 파일의 분실 위험을 피하기 위해 루니버스가 제공하는 파일 저장 서비스라고 할 수 있다.
루니버스 볼트 서비스를 이용하면 사용자의 패스프레이즈(Passphrase)로 한 번 더 암호화되어 서버에 저장되고, 필요할 때 키스토어 파일을 매번 업로드하는 대신 암호화되어 저장된 파일을 불러와 사용자가 입력한 패스프레이즈를 통해 복호화하여 서명할 수 있다.
또한 루니버스 서비스를 이용하는 고객사에 한해 볼트 서비스를 OPEN API도 제공하고 있다고 한다. API Document 문서에 보면 암호화된 키스토어 파일의 저장, 조회, 삭제 API만 제공하므로 암/복호화는 고객사가 직접 해서 API를 사용해야 한다. 그리고 “현재 BETA 버전이고, 어떤 문제에도 책임지지 않는다.”라는 문구가 있어 사용하기에 망설여지는데 어서 믿고 쓸 수 있는 정식 릴리즈 버전이 나오길 기대한다.

사용자 시나리오
1. 사용자가 My Wallet 화면에서 Vault 서비스 토글 버튼을 ON으로 설정한다.
- 루니버스에 회원가입 후 Wallet을 발급받을 때 볼트 서비스 사용 여부를 설정할 수 있다.
2. 팝업 다이얼로그 창에 Wallet 생성 시 다운받았던 키스토어 파일을 업로드하고 패스프레이즈를 입력한다.
3. 볼트 서비스 FE에서 입력받은 패스프레이즈로 키스토어 파일을 암호화한 후 BE API를 호출한다.
- 여기서 패스프레이즈는 어디에도 저장되지 않는다.
4. 볼트 서비스 BE에서 내부적으로 한 번 더 암호화하여 DB에 저장한다.
5. 사용자가 서명이 필요할 때 따로 키스토어 파일을 업로드하지 않고 패스프레이즈만 입력한다.
6. 볼트 서비스에서 암호화된 키스토어 파일을 DB에서 불러와 서비스 내부적으로 1차 복호화 후 사용자의 패스프레이즈를 입력받아 2차 복호화하여 사용하므로 편하게 서명할 수 있다.
암/복호화 과정
루니버스 볼트 서비스의 암/복호화는 CryptoJS 라이브러리를 통한 AES-256(키가 32 byte) 알고리즘을 사용하고 있다.
고급 암호화 표준(Advanced Encryption Standard)이라고 불리는 AES 암호 알고리즘은 DES를 대체한 암호 알고리즘이며, 암호화와 복호화 과정에서 동일한 키를 사용하는 대칭 키 알고리즘이다.
- FE에서는 아래처럼 간단히 암/복호화를 사용하고 있다.
- Crypto-js 라이브러리는 패스프레이즈를 사용할 경우 자동으로 256-bit key를 사용한다.
const AES = require('crypto-js/aes')
...
// 키스토어 암호화
AES.encrypt(keystore, passphrase).toString()
...
// 키스토어 복호화
AES.decrypt(ciphertext, passphrase)
- FE에서 encrypt 한 데이터를 BE에서 받으면 다시 한 번 ‘aes-256-cbc’ 모드로 암호화한다.
- CBC(Cipher-Block Chaining)는 간단히 말하면 첫 블럭의 암호문 결과가 다음 블럭 암호화에도 쓰이는 방식이다.

내부적으로 한 번 더 암호화를 위한 secret은 lambda-dbs-web-be/config/const/config.prd.js 에 암호화되어 저장되어 있다.
key: { ... crypto: { ... vaultSecret: crypto.decrypt('Y1XeOGz+0hQM5... } }
암호화된 secret을 복호화하려면 또 secret이 필요한데 복호화 시 사용할 secret은 process.env.LAMMON_SECRET에 있을 것으로 추정된다.
if (secret === undefined) secret = process.env.LAMMON_SECRET || 'L@M$2AEOU-2O1GU';
secret과 FE로부터 전달받은 encryptedKeystore를 파라미터로 넘겨 lambda-common-lib-node 라이브러리에서 암호화한다.
// 실제 소스에서 알아보기 쉽게 약간 수정하였습니다. function encrypt(encryptedKeystore, secret) { ... const payload = { t: randomBytes(10).toString('base64'), p: encryptedKeystore, }; const [key, iv] = keyAndIv(secret); const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); const buff = Buffer.from(JSON.stringify(payload), 'utf8'); return cipher.update(buff, 'utf8', 'base64') + cipher.final('base64'); }
FE에서 사용자의 패스프레이즈로 한 번, 내부적으로 한 번, 총 두 번 암호화된 데이터를 DB에 저장한다.
복호화는 반대로 내부 secret으로 한번 복호화 한 후 사용자의 패스프레이즈로 최종 복호화하여 사용된다.
패스프레이즈를 분실하거나 탈취당할 경우 복구 또는 안전장치가 없으므로 사용에 주의가 필요하다.
장점
- 사용자가 키스토어 파일을 따로 보관할 필요가 없어 분실의 위험이 없다.
- 단, 패스프레이즈는 분실하지 않도록 주의해야 한다.
- 패스프레이즈만 기억하고 있으면 트랜잭션 서명을 간편하게 할 수 있다.
해시코프(HashiCorp) 볼트와 차이점
1. 고객사에서 루니버스 볼트 서비스 OpenAPI를 이용할 때 1차 암호화는 직접 해야 한다.
- 해시코프라면 Base64 인코딩만 해서 전달하면 암호화는 볼트 서버에서 책임지고 해준다.
- 해시코프 볼트 서버 관리자를 믿을 수 있다면 편하겠지만 직접 암호화를 하는게 더 안전할 수 있을 것 같다.
2. 루니버스 볼트 서비스는 패스프레이즈만 기억하면 된다.
- 해시코프는 권한 있는 토큰과 함께 decryption API를 호출하면 복호화된 데이터를 리턴해 준다. (인증받기 위한 id/pw, 복잡한 권한 관리가 필요하다.)
마치는 말
루니버스는 어떤 식으로 볼트 서비스를 이용하고 있는지 살펴보니 정확히 필요한 부분만 간단하게 구현해 놓은 것 같고, 해시코프는 너무 많은 기능을 갖고 있다는 느낌이 든다.
유명하고 기능이 다양한 외부 서비스를 이용하는 것이 장점도 있겠지만 경험상 불필요하게 많은 기능을 관리하고 그 서비스에 끼워 맞춰야 하는 단점도 있으므로 필요한 부분들을 조금씩 추가해 나간다면 머지않아 루니버스 만의 콤팩트하고 효율적인 볼트 서비스가 될 수 있을 것이다.