现代密码学的最最核心的组件之一就是加密(Encryption)和其对应的解密(Decryption)。Vault自然也不会忽略这个功能,只是它没有将它分离出来,并作为单独作为一个功能,而是巧妙的将它包装成了一种Secret引擎。简单来说,用户可以向Vault请求来加密自己提供的明文(Plain Text),并获取相明文经加密后产生的密文(Cipher Text),其流程和其他引擎很像,都是请求,然后获得动态产生的Secret,而Vault来负责中间繁琐并可能出错的过程。
用来加密的算法可大致分为三类:对称加密(Symmetric),非对称加密(Asymmetric),哈希(Hashing)。Vault提供了全部的支持,下面来简单叙述它们的区别:对称加密要求加密解密时使用相同的密钥(Encryption Key),即要求双方对密钥有相同的看法。而非对称加密,一般也称为公钥(Public Key)加密,需要生成一对密钥,即公钥和私钥(Private Key),在加解密时用到的是对立的密钥。一般公钥是可以公开的,但是私钥不可共享。比如我可以用公钥来加密一段数据,但必须用对应的私钥来解密。这个过程也可以反过来,用私钥加密,然后用公钥来解密,这种用法一般来说不是为了保证信息机密性,而是用来验证私钥持有者的身份,也称为数字签名(Digital Signing)。而哈希算法的作用是将一组数据转换为另一组数据,但是通过生成的数据(ideally)不能还原原来数据,整个过程被认为是单向的,即没有解密的环节,所以整个过程也不涉及密钥。每种不同的加密算法又会细分出不同的算法,比如对称加密常见的有AES,DES,Blowfish,RC等,公钥加密有:RSA,ElGamal,Elliptic-Curve等,哈希常见算法:MD5,SHA等。
让我们先从最直白的对称加密开始,对称加密也是使用到最频繁的加密手段。就比如正当你通过https://csubc.com来阅读这篇文字时,文字的内容在传输过程中就经过了对称加密。先来看一下对称加密的步骤第一步,我先想出一个用来加密和解密的密钥,这个密钥Vault会帮助你保存起来,并且之后密钥可以从Vault中读出:
vault secrets enable -path my-transit transit # same old first step
vault list transit/keys # list all the keys
vault write -f my-transit/keys/my-aes-default # create a default key with default algorithm aes-gcm
vault list transit/keys
vault read my-transit/keys/my-aes-default # get the key info, but not the key's actual value
此时Vault中就存有了名为my-aes-default的密钥,然后就可以用它来进行对称加密了,不管是明文还是生成的密文不会被Vault储存在任何地方。但这里还有个小要求,输入的明文必须是经过base64编码过的才可以:
vault write my-transit/encrypt/my-aes-default plaintext=$(base64 <<< "my universal password")
# output: vault:v1:W3lqdTy9YvmJgoJipMdOfKsuGDNRXfXUIClAqI7oYrzsuw38PzV1vmugx2st1bV8EX8=
vault write my-transit/encrypt/my-aes-default plaintext=$(base64 <<< "my universal password")
# output: vault:v1:iaHbf+m7RWhHvHc5tfaCMJrVJmLFebgnY0iOl3nTf9QKTnpdSDG3UwRE8Gw/uaY3XNg=
这里vault:v1后面的奇怪的文字就是base64的加密后的密文,但我们会发现对于同一个明文同一个密钥两次加密后的密文是不同的,这个问题的答案有兴趣的读者可以去看看aes-gcm这种模式。我们来试试解密看看会不会出现不同的明文:
vault write -field=plaintext my-transit/decrypt/my-aes-default \
ciphertext="vault:v1:W3lqdTy9YvmJgoJipMdOfKsuGDNRXfXUIClAqI7oYrzsuw38PzV1vmugx2st1bV8EX8=" \
| base64 --decode
# output: "my universal password"
vault write -field=plaintext my-transit/decrypt/my-aes-default \
ciphertext="vault:v1:iaHbf+m7RWhHvHc5tfaCMJrVJmLFebgnY0iOl3nTf9QKTnpdSDG3UwRE8Gw/uaY3XNg=" \
| base64 --decode
# output: "my universal password"
因为解密后的返回值也是base64编码的,所以要用--decode来反编码,而vault:v1:是一个前缀,来指明用哪个版本来解密,默认第一个版本为1。这里可以看出,虽然这两条密文不同,但是都可以还原出同样的明文,这种随机性也是密码学中用来防守的重要元素。我们这里先忽略不同算法和模式带来的差别,继续来看下一个问题,如何rotate密钥,密钥的rotation可以提供动态的保护,即会大大降低攻击者对于截获了的密文进行线下破解。
vault read my-transit/keys/my-aes-default
vault write -f my-transit/keys/my-aes-default/rotate # latest version becomes 2
vault read my-transit/keys/my-aes-default
vault write -f my-transit/keys/my-aes-default/rotate # latest version becomes 3
vault read my-transit/keys/my-aes-default
我们对于my-aes-default这个key进行了两次rotation,产生了两个新的版本,但是旧版本依旧存在,为什么呢,因为还有些数据已经使用了旧版本的密钥来加密,那也就需要旧版本的密钥来解密,可以尝试:
vault write -field=plaintext my-transit/decrypt/my-aes-default \
ciphertext="vault:v1:iaHbf+m7RWhHvHc5tfaCMJrVJmLFebgnY0iOl3nTf9QKTnpdSDG3UwRE8Gw/uaY3XNg=" \
| base64 --decode # still got "my universal password"
vault write -field=plaintext my-transit/decrypt/my-aes-default \
ciphertext="vault:v3:iaHbf+m7RWhHvHc5tfaCMJrVJmLFebgnY0iOl3nTf9QKTnpdSDG3UwRE8Gw/uaY3XNg=" \
| base64 --decode # invalid ciphertext: unable to decrypt
那我们总不能一直保留所有的一起用过的密钥吧,这时就要对使用旧的密钥加密的数据进行“升级”,即先用旧的密钥解密,再用新的密钥加密:
vault write my-transit/rewrap/my-aes-default \
ciphertext=vault:v1:iaHbf+m7RWhHvHc5tfaCMJrVJmLFebgnY0iOl3nTf9QKTnpdSDG3UwRE8Gw/uaY3XNg=
# output: vault:v3:nFP9Fsj9p3LrSk9rp1uvplCHOM1OnupxCxleUBldlwm89PqHO/QcCXJ3ELwQbSmbZNk=
vault write -field=plaintext my-transit/decrypt/my-aes-default \
ciphertext="vault:v3:nFP9Fsj9p3LrSk9rp1uvplCHOM1OnupxCxleUBldlwm89PqHO/QcCXJ3ELwQbSmbZNk=" \
| base64 --decode
# output: "my universal password"
这里的v3指的就是最新的版本,当然也可以指明具体用哪个版本来rewrap,而这个新的密文也被v3解密了,还原了明文。