Think Deep,Work Lean

关于证书加密

Posted on By zack

前言

最近搞istio升级时,经常碰到mTLS加密的问题。恰好最近有同事问到双向加密的事情,感觉每次都是看完当时懂,过后又感觉只是一知半解,讲不清楚。此次做个总结。

什么是对称加密?

加密解密使用同一个密钥

加密速度快

问题:客户端与服务端如何使用同一个密钥?

encrypt(明文,秘钥) = 密文

decrypt(密文,秘钥) = 明文

什么是非对称加密?

非对称加密,有公私钥之分

加密速度慢

公钥加密的数据,则只能用私钥来解密(加密);

私钥加密的数据,只能用公钥来解密(签名)。

公钥是可以公开的密钥,可以在网络上传输。

由于加密速度慢,通常会结束对称加密一起使用。

通过非对称加密方式,传递对称加密的私钥;真正的通信使用对称加密

什么是mtls?

mtls即双向认证

客户端验需要证服务器端证书: 如在访问https的时候,使用curl -k可以跳过客户端对服务端证书的验证。服务端生成证书+密钥,客户端校验服务端的证书。在通过浏览器访问https加密网站的时候,服务端会把它的证书发给客户端,浏览器内置的CA根证书会校验该证书的过期时间、域名地址等是否合法。

服务端需要验证客户端证书:双向认证会多这一步,此时客户端需要带上自己的证书+密钥来访问服务端。

如下:

[root@ssa3 ~]# curl https://127.0.0.1:2379/version --cacert /etc/ssl/etcd/ssl/ca.pem --cert /etc/ssl/etcd/ssl/admin-ssa3.pem   --key /etc/ssl/etcd/ssl/admin-ssa3-key.pem
{"etcdserver":"3.3.12","etcdcluster":"3.3.0"}

在访问https网页的时候,具体是怎样的传输过程?

单向认证 双向认证

思考:早期网银给你一个数字证书,需要自己安装在浏览器上面,这个证书是什么证书?

  1. 有可能是ca证书,如果早期银行的证书不在浏览器上的CA根证书链里面,此时服务器发过来公钥(即证书),此时浏览器不识别,就会导致连接不成功。
  2. 有没有可能是双向认证,这个证书是客户端证书,对客户端数据加密后让服务端来验证客户的证书? — 感觉这种可能性不大,因为一般需要证书+密钥,缺密钥。

关于jwt

格式: base64(Header).base64(Payload).Signature

位于七层,主要用于保存请求登陆信息。

有过期时间。

为什么需要Jwt?

如果要实现多点登陆,一般是使用cookie与session来解决,客户端存cookie,登陆成功后,把登陆的信息hash加密后存到服务端session中。服务端给客户端返回sessionID,下次浏览器访问的时候,带上这个seesionID,服务端就能知道该请求的登陆状态。

但是如果跨域,就要求session持久化并做共享。多了一个持久化层,多了一个故障点。

为了减少这个故障点,可以使用JWT。不用服务端来存session,当服务端认证通过后,会直接给客户端返回一个token,客户端在访问的时候,直接带上自己的认证信息,即JWT。

什么是Jwt?

JWT签名信息是对令牌头负载签名三者通过令牌头中指定的哈希算法计算出来的摘要值。

摘要是惟一的标识,例如从网上下载镜像,会附带md5校验值。但是不能用这个md5值推导出镜像。

  1. Header: Json对象,存放加密算法类型

  2. Payload: Json对象,存储实际要传输的数据

iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
  1. Signature签名,该部分是对前两者的签名,防止数据被篡改,secret由服务器端保存。生成jwt的过程在服务端完成,然后把Jwt发给客户端。签名公式为:
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

根据上面的公式可知,前两个Json对象使用base64来加密;但是可以使用base64 --decode来解密查看其内容。因此任何人都可以看,那这样加密的目的是什么?
因为对于用户的一些特殊字段,在数据传输过程中容易被转义,如斜杠等。此时使用base64来解决该问题。另外在传输二进制的时候,也通常使用base64加密传输。

参考阮一峰jwt入门

如何使用jwt?

客户端收到jwt后,可以存在cookie中,但是这样无法跨域。

最好的做法是存在HTTP Header请求头中的Authorization字段里面

Authorization: Bear <token>

还有另一种做法:跨域的时候,放在Post请求体中。

如:在KubeSphere中使用JWT

使用token直接登陆的方法:

  1. 先获取secret:

[root@master1 ~]# kubectl -n kubesphere-system get secret kubesphere-secret -o yaml,获取token

  1. 将token base64 decode出来

hugo@zack:/Users/hugo $ echo "ZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SmxiV0ZwYkNJNkltRmtiV2x1UUd0MVltVnpjR2hsY21VdWFXOGlMQ0oxYzJWeWJtRnRaU0k2SW1Ga2JXbHVJaXdpZEc5clpXNWZkSGx3WlNJNkluTjBZWFJwWTE5MGIydGxiaUo5LlR2UG1IaFlGT2NacGpXamtkS3JrMXVNdktIQlhnREdTck5GU0k1Q3ROWVU="|base64 --decode

  1. 使用Bearer token
hugo@zack:/Users/hugo $ curl http://139.198.19.140:30011/kapis/tenant.kubesphere.io/v1alpha2/events -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFkbWluQGt1YmVzcGhlcmUuaW8iLCJ1c2VybmFtZSI6ImFkbWluIiwidG9rZW5fdHlwZSI6InN0YXRpY190b2tlbiJ9.TvPmHhYFOcZpjWjkdKrk1uMvKHBXgDGSrNFSI5CtNYU"

jwt

jwt与tls加密冲突吗?

tls主要解决传输加密;jwt解决的是请求认证的问题。一个四层、一个七层,不冲突。

由于只要拿到jwt后,就可以登陆服务,因此最好使用https来访问,并设置合适的过期时间。

什么是证书?

如何生成证书?

关于如何创建key pem可以参考在安装k8s时候etcd的证书密钥

总结:

  1. 先创建ca密钥
  2. 根据密钥创建ca证书
    规律:先创建密钥,再创建证书,证书是根据密钥创建出来的

节点的证书:

  1. 先创建密钥
  2. 再创建csr证书请求
  3. 最后创建出证书
# Root CA
if [ -e "$SSLDIR/ca-key.pem" ]; then
    # Reuse existing CA
    cp $SSLDIR/{ca.pem,ca-key.pem} .
else
    openssl genrsa -out ca-key.pem  > /dev/null 2>&1
    openssl req -x509 -new -nodes -key ca-key.pem -days  -out ca.pem -subj "/CN=etcd-ca" > /dev/null 2>&1
fi
# ETCD member
if [ -n "$MASTERS" ]; then
    for host in $MASTERS; do
        cn="${host%%.*}"
        # Member key
        openssl genrsa -out member-${host}-key.pem  > /dev/null 2>&1
        openssl req -new -key member-${host}-key.pem -out member-${host}.csr -subj "/CN=etcd-member-${cn}" -config ${CONFIG} > /dev/null 2>&1
        openssl x509 -req -in member-${host}.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out member-${host}.pem -days  -extensions ssl_client -extfile ${CONFIG} > /dev/null 2>&1
        # Admin key
        openssl genrsa -out admin-${host}-key.pem  > /dev/null 2>&1
        openssl req -new -key admin-${host}-key.pem -out admin-${host}.csr -subj "/CN=etcd-admin-${cn}" > /dev/null 2>&1
        openssl x509 -req -in admin-${host}.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out admin-${host}.pem -days  -extensions ssl_client  -extfile ${CONFIG} > /dev/null 2>&1
    done
fi

https://github.com/kubernetes-sigs/kubespray/blob/master/roles/etcd/templates/make-ssl-etcd.sh.j2

参考istio如何生成证书的,有完整的方法

https://istio.io/latest/docs/tasks/traffic-management/ingress/secure-ingress/#generate-client-and-server-certificates-and-keys

https://github.com/istio/istio/blob/master/security/samples/plugin_ca_certs/gen_certs.sh#L33

参考kubesphere如何生成证书的

https://github.com/kubesphere/kubesphere/blob/master/hack/generate_certs.sh

tls与sni什么关系

istio的virtualservice中支持http tcp tls三种协议,为什么需要有tls协议?

virtualservice

tls是一层加密协议,虽然也位于传输层,却在tcp之上。因此tls协议也可以按tcp协议把流量做分流;就好比是http的流量,也可以使用tcp流量做分流。

那什么是sni呢,sni tls跟istio并没有什么关系。只不过istio在virtualservice使用tls协议,对tls协议多了一层支持。就好比区分它是http流量后,可以使用Http的一些属性更好地来支持它。

那么到底什么是sni呢?

很多提供tls的服务器,其实是可以配置多个证书,同样是443端口,可以有两个证书,比如hello.com和world.com (edited)

客户端在发起请求的时候会指定sni,当然也有默认的证书,但是如果sni指定错误,会导致tls流程错误,因为证书就返回错误,客户端会出现校验不通过。https://tools.ietf.org/html/rfc6066#section-3

类似于nginx可以通过vhosts的不同指向不同的后端服务。

http的场景如下:

hello.com world.com 指向同一个IP+端口,后端通过匹配hosts来区分流量。

这个是http层面的了 就是虚拟服务器。

但是在tls握手阶段还没有hostname,但是客户端需要指定需要哪个证书。

这个时候只能通过tls的sni来做,sni的指定是由tls客户端做的。

也就是说sni的作用主要在提供多证书才有用,对于一个端口一个证书的意义不大,因为有默认证书。

参考

http://icyfenix.cn/architect-perspective/general-architecture/system-security/transport-security.html
https://www.jianshu.com/p/29e0ba31fb8d