标签 加密 下的文章

使用Go基于国密算法实现双向认证

本文永久链接 – https://tonybai.com/2022/07/17/two-way-authentication-using-go-and-sm-algorithm

国内做2B(to Biz)或2G(to Gov)产品和解决方案的企业都绕不过国密算法,越来越多的国内甲方在采购需求中包含了基于国密算法的认证、签名、加密等需求。对于国内的车联网平台来说,支持基于国密的双向认证也是大势所趋。在这篇文章中,我就来说说如何基于国密算法实现双向认证,即使用国密算法的安全传输层双向认证


一. 简要回顾基于TLS的双向认证

《Go语言精进之路》第2册的第51条中,我详细介绍了TLS的建连握手与双向认证过程,并对非对称加密与公钥证书的原理做了系统全面的讲解。为了让大家更好地理解后面的内容,这里简单回顾一下基于TLS的双向认证

TLS,全称Transport Layer Security,即安全传输层。其前身为SSL(Secure Socket Layer)。TLS是建构在TCP传输层之上和应用层之下的、为应用层提供端到端安全连接和传输服务的虚拟协议层

应用层基于TLS的通信都是加密的(如上图所示),保证了传输数据的安全,即便被窃听,攻击者也无法拿到明文数据(密钥够长,加密算法强度够强的前提下)。对于应用开发者而言,重点在于TLS连接的建立过程,连接一旦建立,后续的加解密传输过程就很容易了。

TLS连接的建立过程称为TLS握手(handshake),握手的过程见下图(适用于TLS 1.2):


上图引自《Go语言精进之路》第2册的图51-5

关于握手的各个步骤的详细说明,大家可以参考《Go语言精进之路》第2册的第51条中的内容,这里不赘述。

从图中我们可以看到:TLS连接的建立过程需要数字证书的参与,而数字证书主要用于对通信双方的身份进行验证以及参与双方会话密钥的协商与生成。一般情况下,客户端会校验服务端的公钥证书,服务端不会校验客户端公钥证书。但在一些安全级别较高的系统中,服务端也会要求校验客户端的公钥证书(TLS握手阶段,服务端向客户端发送CertificateRequest请求)。

下面我们就来看一个基于TLS的双向认证的实例。

二. 基于TLS双向认证的示例

我们先来看看示例开发与执行的环境并创建相关的数字证书。

1. 环境与数字证书

我们在Ubuntu 20.04.3 LTS环境使用Go 1.18版本开发和执行该示例。示例是一个echo server,即将client端发来的数据重新发回client端,下面是示意图:

开发基于TLS的应用离不开数字证书,因此在开发程序之前,我们先来生成server与client所用的各类公钥数字证书。

在开发和测试环境,我们可以使用自签发的公钥数字证书。我们可以先生成自用的CA私钥与证书,然后利用该CA签发我们所需的服务端和客户端的公钥证书。制作证书最著名的工具是openssl,不过openssh使用起来较为复杂,这些年一些开发者体验更好的工具也逐渐成熟,比如由Go项目前安全负责人Filippo Valsorda开源的mkcert就是一个不错的工具,本文就使用这个工具建立CA并签发制作各类证书。

我们先来安装mkcert工具:

$go install filippo.io/mkcert@latest
go: downloading filippo.io/mkcert v1.4.4
go: downloading golang.org/x/net v0.0.0-20220421235706-1d1ef9303861
go: downloading software.sslmate.com/src/go-pkcs12 v0.2.0
go: downloading golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29

接下来,生成并安装local CA根证书:

$mkcert -install
Created a new local CA:
The local CA is now installed in the system trust store!

从mkcert的输出来看,CA私钥和证书被安装到所谓system trust store中。这个system trust store在不同平台上的位置不同。在linux上有如下几个位置:

// github.com/FiloSottile/mkcert/truststore_linux.go 

func init() {
    switch {
    case binaryExists("apt"):
        CertutilInstallHelp = "apt install libnss3-tools"
    case binaryExists("yum"):
        CertutilInstallHelp = "yum install nss-tools"
    case binaryExists("zypper"):
        CertutilInstallHelp = "zypper install mozilla-nss-tools"
    }
    if pathExists("/etc/pki/ca-trust/source/anchors/") {
        SystemTrustFilename = "/etc/pki/ca-trust/source/anchors/%s.pem"
        SystemTrustCommand = []string{"update-ca-trust", "extract"}
    } else if pathExists("/usr/local/share/ca-certificates/") {
        SystemTrustFilename = "/usr/local/share/ca-certificates/%s.crt"
        SystemTrustCommand = []string{"update-ca-certificates"}
    } else if pathExists("/etc/ca-certificates/trust-source/anchors/") {
        SystemTrustFilename = "/etc/ca-certificates/trust-source/anchors/%s.crt"
        SystemTrustCommand = []string{"trust", "extract-compat"}
    } else if pathExists("/usr/share/pki/trust/anchors") {
        SystemTrustFilename = "/usr/share/pki/trust/anchors/%s.pem"
        SystemTrustCommand = []string{"update-ca-certificates"}
    }
}

在我的ubuntu 20.04环境中,CA的公钥证书被安装(install)在/usr/local/share/ca-certificates下面了:

$ls -l /usr/local/share/ca-certificates/
total 4
-rw-r--r-- 1 root root 1631 Jul  3 16:22 mkcert_development_CA_333807542491031300702675758897110223851.crt

生成的CA私钥在哪里呢?我们可以通过-CAROOT参数获得该位置:

$mkcert -CAROOT
/home/tonybai/.local/share/mkcert

$ls -l /home/tonybai/.local/share/mkcert
total 8
-r-------- 1 tonybai tonybai 2484 Jul  3 16:22 rootCA-key.pem
-rw-r--r-- 1 tonybai tonybai 1631 Jul  3 16:22 rootCA.pem

这里的rootCA.pem与系统信任区中的mkcert_development_CA_333807542491031300702675758897110223851.crt与rootCA.pem内容是一样的。后者是mkcert将rootCA.pem安装到系统可信store后的结果。通过mkcert -uninstall可以删除/usr/local/share/ca-certificates下面的CA公钥证书。但/home/tonybai/.local/share/mkcert下的CA私钥与证书不会被删除。后续若再执行mkcert install,CA证书不会重新生成,现有的rootCA.pem还会被install到/usr/local/share/ca-certificates下面。

接下来我们分别server端和client端的私钥与证书。

server端key和cert:

$mkcert -key-file key.pem -cert-file cert.pem example.com 

Created a new certificate valid for the following names
 - "example.com"

The certificate is at "cert.pem" and the key at "key.pem"

It will expire on 3 October 2024

我们可以通过下面命令查看证书内容:

$openssl x509 -in cert.pem -noout -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            fc:cc:96:17:55:2d:70:e8:67:3e:b2:25:a9:b8:a3:80
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: O = mkcert development CA, OU = tonybai@tonybai, CN = mkcert tonybai@tonybai
        Validity
            Not Before: Jul  7 09:05:09 2022 GMT
            Not After : Oct  7 09:05:09 2024 GMT
        Subject: O = mkcert development certificate, OU = tonybai@tonybai
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:a6:d1:00:f7:da:03:d0:06:17:cb:ee:b4:99:30:
                    20:66:d0:78:b0:94:67:0b:7a:37:d2:76:21:71:9a:
                    7a:17:d6:44:0a:7b:f1:24:71:2f:ed:b5:67:66:a1:
                    1f:b0:e6:3b:18:66:de:f4:83:78:9a:bc:f5:ae:88:
                    23:a1:f9:7d:7c:3e:7f:a8:f9:9f:54:d0:68:48:b9:
                    d0:56:10:a0:84:0b:cf:a8:bc:b8:74:3f:3c:27:db:
                    ff:28:1d:63:e8:79:a6:93:44:a8:14:43:53:bf:e8:
                    ca:ee:bf:4c:63:f7:23:51:e6:a2:8d:0b:9a:7d:95:
                    2e:bc:37:ae:6d:ea:9e:0e:e6:e0:c5:8e:07:0c:d4:
                    9b:50:30:de:31:c9:97:ee:ac:7e:33:ab:0d:6f:87:
                    f3:70:2b:22:26:8d:a8:95:8e:1f:0e:b7:61:71:e8:
                    36:06:a7:f4:d8:d2:f6:89:12:26:fd:7e:6b:19:a2:
                    2a:4c:d7:cb:7e:09:fc:65:86:be:b6:c2:0b:fb:b5:
                    d8:63:07:aa:ba:59:ab:fc:34:0d:4a:d1:93:dd:62:
                    b0:3a:cd:e1:21:79:13:e4:f4:45:00:f7:10:a1:bc:
                    c7:51:38:84:c4:75:22:5e:5f:a9:11:07:34:16:9f:
                    ad:c7:94:af:57:30:17:77:49:14:6e:16:ff:d8:00:
                    78:11
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage:
                TLS Web Server Authentication
            X509v3 Authority Key Identifier:
                keyid:A8:C4:06:2D:2C:25:71:EC:08:C8:1A:92:9A:F2:52:87:22:6E:85:2D

            X509v3 Subject Alternative Name:
                DNS:example.com
    Signature Algorithm: sha256WithRSAEncryption
         be:6e:90:60:bd:43:b9:3a:09:14:c2:44:22:88:a6:af:e5:22:
         d3:97:19:64:8b:59:5d:60:33:36:01:a1:4e:01:eb:7e:5c:6a:
         48:c4:04:a6:0a:e4:91:95:db:5a:2c:c8:e9:93:fa:37:34:6d:
         81:d1:96:ed:5b:67:ae:27:e3:d3:ea:ee:5c:74:0f:6e:f1:48:
         72:d2:75:85:a1:70:0f:a0:9a:73:7a:ca:b8:7b:92:46:27:73:
         e5:f8:ec:72:f8:fc:ac:5f:22:68:0c:d6:8c:20:5b:93:e1:52:
         17:79:57:71:33:5b:98:05:11:8a:cb:d4:3c:b2:24:4b:7b:c5:
         32:8f:ae:1f:a5:af:9d:3a:9b:bb:fc:46:8a:d6:48:39:86:de:
         f3:f7:54:03:45:8d:bd:40:91:26:d2:29:0a:c4:91:cf:b2:5c:
         41:d5:66:24:02:6d:60:22:ea:78:0d:b0:66:80:b9:5d:03:27:
         09:c7:aa:61:1b:ee:e4:08:21:7e:be:bb:13:8a:fb:d8:9e:24:
         5f:5b:a2:4a:d5:db:be:a2:84:74:03:fb:04:37:d0:b3:c4:b7:
         4e:3e:31:a7:2d:5d:62:bd:aa:68:3c:84:d9:32:cb:f2:93:7a:
         3a:8a:2b:c3:81:76:f0:b5:f5:3c:d4:69:8d:5e:f8:39:74:88:
         2b:56:7f:2b:4c:f9:39:2a:f2:4d:15:75:a1:f3:62:ee:57:ce:
         f7:33:c7:cc:a6:97:25:f0:66:bf:5d:5b:c2:d7:d3:ee:20:be:
         c3:5f:fb:9a:50:59:b8:e7:ea:d2:4c:35:9d:48:3f:93:63:96:
         3c:52:dd:b8:d6:ba:1f:30:18:2e:c4:3d:3a:03:66:e1:a3:48:
         6e:a0:5d:b0:0b:65:d4:40:9e:da:5c:36:b1:ac:6b:9e:1f:01:
         69:8a:92:63:7d:27:79:42:bd:d4:f5:e2:d3:bf:8e:97:2f:57:
         ae:0b:f8:c1:b1:35:47:d0:4e:77:b0:e7:88:69:4b:44:dc:01:
         6e:6e:4d:87:e2:71

接下来,我们再生成client端的key和cert。client端的cert专门用来提供给server端进行证书验证的,我们需要向mkcert传入-client选项生成client端证书:

$mkcert -client -key-file client-key.pem -cert-file client-cert.pem client1

Created a new certificate valid for the following names
 - "client1"

The certificate is at "client-cert.pem" and the key at "client-key.pem"

It will expire on 3 October 2024

同样,我们可以通过下面命令查看客户端证书的内容:

$openssl x509 -in client-cert.pem -noout -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            62:59:40:5c:e7:5a:61:74:73:bf:08:b0:d9:a7:d4:a1
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: O = mkcert development CA, OU = tonybai@tonybai, CN = mkcert tonybai@tonybai
        Validity
            Not Before: Jul  7 09:11:27 2022 GMT
            Not After : Oct  7 09:11:27 2024 GMT
        Subject: O = mkcert development certificate, OU = tonybai@tonybai
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:e5:25:c6:a1:c9:e2:5f:64:72:bd:ed:fc:24:fa:
                    12:8d:9c:30:52:8d:d8:5a:e7:f4:0c:b5:d5:0a:ef:
                    06:26:e3:06:54:54:cc:72:77:4e:22:cd:22:04:c0:
                    08:2e:94:2d:0f:cc:e8:9f:b9:c5:f4:13:8e:d1:f4:
                    bb:64:9d:1a:74:1b:e3:8c:95:2c:18:44:ec:e7:2c:
                    ec:0c:19:0f:e1:e6:1a:22:e7:3e:a6:1b:35:6e:05:
                    5f:c3:04:3f:1a:0f:c4:55:6f:ff:15:a0:a0:de:44:
                    5c:2d:3d:0b:dc:8a:01:ca:d2:2a:71:9d:b7:3a:d2:
                    10:9f:79:76:e0:a7:14:aa:d8:f0:90:bd:7c:4d:2d:
                    45:e6:16:ab:1d:03:7f:d8:97:4f:4d:41:13:76:72:
                    35:f2:41:b7:f1:3b:a8:42:d4:79:39:fd:f6:8d:10:
                    d1:54:06:60:6a:79:04:6c:6f:05:37:9c:4e:e7:ba:
                    9d:87:e8:05:65:9a:22:56:91:cb:03:bd:89:42:16:
                    66:92:bf:df:50:27:f2:81:89:c0:c5:46:f7:01:e8:
                    80:d0:4d:2e:ae:7f:5a:e9:fa:69:f3:50:c4:58:48:
                    dc:b5:20:13:01:3a:ac:fd:a8:69:2d:20:a9:55:cc:
                    90:4a:f1:f7:3f:9e:3b:7a:cb:77:c7:d2:c4:2b:4f:
                    4c:09
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage:
                TLS Web Client Authentication, TLS Web Server Authentication
            X509v3 Authority Key Identifier:
                keyid:A8:C4:06:2D:2C:25:71:EC:08:C8:1A:92:9A:F2:52:87:22:6E:85:2D

            X509v3 Subject Alternative Name:
                DNS:client1
    Signature Algorithm: sha256WithRSAEncryption
         a6:68:a8:b3:cf:8c:8c:f6:03:56:68:e4:d3:02:cd:ec:8d:fa:
         7f:73:56:c2:91:fa:d8:65:82:a7:f5:d9:8b:32:2a:3b:f9:59:
         71:0c:f8:d3:b6:d3:b3:11:99:f6:f6:d1:ab:d9:1e:fc:bd:f5:
         71:d9:35:4e:0e:fb:f2:f9:65:12:f2:1d:26:77:7d:eb:2c:52:
         80:2c:05:64:0f:99:35:83:31:b0:eb:71:85:04:48:d6:f6:29:
         92:81:f5:22:ee:77:8b:3d:e8:66:6a:5f:59:69:73:15:bb:69:
         46:e9:df:8c:7c:1d:28:b5:71:ed:2e:ca:8e:d3:08:da:85:b4:
         6c:26:89:85:16:c3:9a:e4:45:ef:3d:16:a2:32:45:70:e5:7e:
         82:e1:55:32:e7:1a:63:6b:56:8f:11:70:53:6f:71:d8:e0:76:
         bc:af:bd:dc:53:d6:fb:f0:b6:29:5f:3b:3f:dd:5c:58:b4:f0:
         d2:bb:63:d6:7f:b6:5f:29:ac:43:fa:56:f6:38:a4:03:6e:f3:
         b6:0d:e3:94:4c:0e:de:28:0c:63:27:94:5c:c8:15:78:c1:3b:
         a3:9f:f3:7f:d8:79:c1:ee:23:da:42:ef:25:40:a1:b9:e4:54:
         c4:d0:6b:81:b8:c1:b6:78:aa:d9:25:31:25:fe:5c:a8:d4:46:
         61:38:2e:6e:ba:34:b6:21:cb:66:47:9e:4f:ca:e2:6a:6a:06:
         60:d4:cb:fd:e6:a2:d5:d3:44:40:f1:f9:a9:0d:38:47:a4:20:
         1a:59:4f:14:ab:ab:e9:20:53:91:1b:0e:57:7b:2e:72:d6:1c:
         73:37:d3:17:f6:65:75:ef:27:19:ee:32:2d:ac:ca:46:c4:aa:
         ea:60:d8:6c:fa:62:ad:d4:34:f5:f9:57:48:8f:c0:b3:30:0e:
         13:ec:69:7b:52:97:d6:f5:fa:16:bb:38:c6:03:2f:1a:21:6e:
         bb:69:2a:74:dc:3c:71:3e:af:91:dd:28:86:ca:c8:3b:58:29:
         07:3b:5c:67:3d:31

我们看到:client-cert.pem与cert.pem在“X509v3 Extended Key Usage”一项有差别,client-pert.pem除了包含TLS Web Server Authentication,还增加了TLS Web Client Authentication。

2. echoserver与echoclient

Go标准库提供了tls的基本实现,支持tls1.2和1.3版本。下面是echoserver的主要源码:

// github.com/bigwhite/experiments/gmssl-examples/tls/server/server.go
func main() {
    cert, err := tls.LoadX509KeyPair("./certs/cert.pem", "./certs/key.pem")
    if err != nil {
        fmt.Println("load x509 keypair error:", err)
        return
    }
    cfg := &tls.Config{
        Certificates: []tls.Certificate{cert},
        ClientAuth:   tls.RequireAndVerifyClientCert,
    }
    listener, err := tls.Listen("tcp", ":18000", cfg)
    if err != nil {
        fmt.Println("listen error:", err)
        return
    }

    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("accept error:", err)
            return
        }
        fmt.Println("accept connection:", conn.RemoteAddr())
        go func() {
            for {
                // echo "request"
                var b = make([]byte, 16)
                _, err := conn.Read(b)
                if err != nil {
                    fmt.Println("connection read error:", err)
                    conn.Close()
                    return
                }

                fmt.Println(string(b))
                _, err = conn.Write(b)
                if err != nil {
                    fmt.Println("connection write error:", err)
                    return
                }
            }
        }()
    }
}

我们看到基于tls的echoserver与一个普通的tcp server的代码差别不多,最主要就是在创建listener时传入了一个tls.Config结构,这个结构中有tls握手(handshake)所需要的全部信息,包括server端使用的私钥与证书(通过LoadX509KeyPair加载)以及对client端进行证书校验的标志(ClientAuth: tls.RequireAndVerifyClientCert)。一旦连接建立,握手成功,后续的数据读写都和基于tcp连接的普通服务端程序无异。

下面是echoclient的主要源码:

// github.com/bigwhite/experiments/gmssl-examples/tls/client/client.go

func main() {
    cert, err := tls.LoadX509KeyPair("./certs/client-cert.pem", "./certs/client-key.pem")
    if err != nil {
        fmt.Println("load x509 keypair error:", err)
        return
    }

    conn, err := tls.Dial("tcp", "example.com:18000", &tls.Config{
        Certificates: []tls.Certificate{cert},
    })
    if err != nil {
        fmt.Println("failed to connect: " + err.Error())
        return
    }
    defer conn.Close()

    fmt.Println("connect ok")
    for i := 0; i < 100; i++ {
        _, err := conn.Write([]byte("hello, gm"))
        if err != nil {
            fmt.Println("conn write error:", err)
            return
        }

        var b = make([]byte, 16)
        _, err = conn.Read(b)
        if err != nil {
            fmt.Println("conn read error:", err)
            return
        }
        fmt.Println(string(b))
        time.Sleep(time.Second)
    }
}

client端的代码更为简单一些,只需load client端使用的私钥与证书,然后传给tls.Config实例。tls.Dial使用该Config实例便可以顺利连接echoserver。

3. 用于验证对方证书的CA证书

在上面两个程序中都没有提到CA证书,那么server端和client端用什么去验证对方的公钥证书呢?其实依旧是用mkcert创建的CA证书去验证,只不过由于mkcert将CA证书安装到了操作系统trust store路径中,程序可以在系统CA证书中自动找到用来验证client和server两端公钥证书的CA证书,因此无需在程序中显式加载特定CA证书。

如果我们执行mkcert -uninstall,那么client程序在与server作tls handshake时就会报如下错误:

// client程序的输出日志:
failed to connect: x509: certificate signed by unknown authority

// server程序的输出日志:
accept connection: 127.0.0.1:56734
connection read error: remote error: tls: bad certificate

三. 密码算法在TLS握手以及后续通信过程中的作用

在TLS握手阶段,密码算法起到了关键作用。那在握手的每个阶段都在使用什么算法呢?我们看看下面使用curl命令访问https站点的输出:

$curl -v https://baidu.com
*   Trying 220.181.38.148:443...
* TCP_NODELAY set
* Connected to baidu.com (220.181.38.148) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
... ...

在这段内容中,我们看到这样一行输出:

SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256

这行后面的ECDHE-RSA-AES128-GCM-SHA256就是在握手过程中以及后续通信阶段会使用到的算法。这样的一串称为密码套件(cipher suite),在SSL协议时代被称为cipher kinds

密码套件一般由多个用途不同的密码算法名称组合而成(套件中的算法都是要配合使用的,单独使用没法保证信息安全传输)。下面是openssl-1.1.1f支持的密码套件列表:

$openssl ciphers -V | column -t
0x13,0x02  -  TLS_AES_256_GCM_SHA384         TLSv1.3  Kx=any       Au=any    Enc=AESGCM(256)             Mac=AEAD
0x13,0x03  -  TLS_CHACHA20_POLY1305_SHA256   TLSv1.3  Kx=any       Au=any    Enc=CHACHA20/POLY1305(256)  Mac=AEAD
0x13,0x01  -  TLS_AES_128_GCM_SHA256         TLSv1.3  Kx=any       Au=any    Enc=AESGCM(128)             Mac=AEAD
0xC0,0x2C  -  ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2  Kx=ECDH      Au=ECDSA  Enc=AESGCM(256)             Mac=AEAD
0xC0,0x30  -  ECDHE-RSA-AES256-GCM-SHA384    TLSv1.2  Kx=ECDH      Au=RSA    Enc=AESGCM(256)             Mac=AEAD
0x00,0x9F  -  DHE-RSA-AES256-GCM-SHA384      TLSv1.2  Kx=DH        Au=RSA    Enc=AESGCM(256)             Mac=AEAD
0xCC,0xA9  -  ECDHE-ECDSA-CHACHA20-POLY1305  TLSv1.2  Kx=ECDH      Au=ECDSA  Enc=CHACHA20/POLY1305(256)  Mac=AEAD
0xCC,0xA8  -  ECDHE-RSA-CHACHA20-POLY1305    TLSv1.2  Kx=ECDH      Au=RSA    Enc=CHACHA20/POLY1305(256)  Mac=AEAD
0xCC,0xAA  -  DHE-RSA-CHACHA20-POLY1305      TLSv1.2  Kx=DH        Au=RSA    Enc=CHACHA20/POLY1305(256)  Mac=AEAD
0xC0,0x2B  -  ECDHE-ECDSA-AES128-GCM-SHA256  TLSv1.2  Kx=ECDH      Au=ECDSA  Enc=AESGCM(128)             Mac=AEAD
0xC0,0x2F  -  ECDHE-RSA-AES128-GCM-SHA256    TLSv1.2  Kx=ECDH      Au=RSA    Enc=AESGCM(128)             Mac=AEAD
0x00,0x9E  -  DHE-RSA-AES128-GCM-SHA256      TLSv1.2  Kx=DH        Au=RSA    Enc=AESGCM(128)             Mac=AEAD
0xC0,0x24  -  ECDHE-ECDSA-AES256-SHA384      TLSv1.2  Kx=ECDH      Au=ECDSA  Enc=AES(256)                Mac=SHA384
... ...

我们看看上面输出的后四列的含义:

  • 第四列(Kx)

Kx代表key exchange,这一列是密钥交换算法,常见的密钥交换算法包括DH(Diffie-Hellman)、DHE(Diffie-Hellman Ephemeral)、ECDHE(在DHE算法的基础上利用了ECC椭圆曲线特性)等。在tls握手阶段,密钥交换算法用于在不安全的通道上协商会话加密(对称加密)算法密钥。

  • 第五列(Au)

Au代表authentication,这一列是身份认证算法,通常是非对称加密算法,比如:RSA、ECDSA等。该算法用于服务端与客户端相互验证对方的公钥数字证书时。

  • 第六列(Enc)

Enc代表对称加密算法,比如:AES、CHACHA20等,对称加密算法在tls握手后用于对客户端与服务端交互的数据进行加解密,它的加解密性能要比非对称加密算法快上很多。

  • 第七列(Mac)

Mac代表Message Authentication Code,消息认证码算法,本质上是一个hash函数,用于计算数据的摘要值,是常用的用于保证消息数据完整性的工具。常见的算法有SHA1、SHA256等。

有了这些知识,我们再回到前面的ECDHE-RSA-AES128-GCM-SHA256,我们可以知道这个密码套件使用ECDHE作为密钥交换算法,使用RSA作为服务器认证算法(非对称加密),使用AES128-GCM作为对称加密算法,使用SHA256作为消息认证码算法。

注:TLS 1.3版本的握手过程已经修改,仅需对称加密和Mac算法参与,因此TLS 1.3的密码套件格式已经变化。在TLS 1.3中,密码套件仅用于协商对称加密和MAC算法。对应的,我们看到上面OpenSSL输出的TLSv1.3版本的密码套件(如TLS_AES_256_GCM_SHA384、TLS_CHACHA20_POLY1305_SHA256等)的Kx和Au都是any。换句话说:TLSv1.2和TLSv1.3版本的密码套件并不兼容,不能混用(TLS v1.3的密码套件不能用在TLS v1.2版本中,反之亦然)。

Go标准库(Go 1.18.3)内置支持的cipher suite如下:

// $GOROOT/src/crypto/tls/cipher_suites.go
func CipherSuites() []*CipherSuite {
    return []*CipherSuite{
        {TLS_RSA_WITH_AES_128_CBC_SHA, "TLS_RSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
        {TLS_RSA_WITH_AES_256_CBC_SHA, "TLS_RSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
        {TLS_RSA_WITH_AES_128_GCM_SHA256, "TLS_RSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
        {TLS_RSA_WITH_AES_256_GCM_SHA384, "TLS_RSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},

        {TLS_AES_128_GCM_SHA256, "TLS_AES_128_GCM_SHA256", supportedOnlyTLS13, false},
        {TLS_AES_256_GCM_SHA384, "TLS_AES_256_GCM_SHA384", supportedOnlyTLS13, false},
        {TLS_CHACHA20_POLY1305_SHA256, "TLS_CHACHA20_POLY1305_SHA256", supportedOnlyTLS13, false},

        {TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
        {TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
        {TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
        {TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
        {TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
        {TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
        {TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
        {TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
        {TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", supportedOnlyTLS12, false},
        {TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", supportedOnlyTLS12, false},
    }
}

每个密码套件具有唯一标识值(value),这些值在https://www.iana.org/assignments/tls-parameters/tls-parameters.xml中有标准参考。

我们看到这些密码套件中的算法都是一些耳熟能详的国际标准密码算法,但并没有看到我们国家的国密的影子?我们国家的国密算法都有哪些?是否可以作为TLS握手过程使用的密码套件的一部分呢?如何基于国密算法实现一个安全传输层呢?我们接下来就正式进入国密算法(前面的铺垫有些长^_^)。

四. 国家商用密码(国密)介绍

密码算法是最基础、最重要的密码技术。国家密码管理局近十年来,先后发布了祖冲之序列密码算法、SM2~SM9等商用密码系列(SM系列)算法,构成了包含序列密码算法、对称密码算法、非对称密码算法、密码杂凑算法和标识密码算法等在内的完整、自主国产密码算法体系。2019年10月26日,第十三届全国人民代表大会常务委员会第十四次会议表决通过了《中华人民共和国密码法》,并于2020年1月1日起施行。这些算法目前也已经成为ISO/IEC相关国际标准了(只是在国外的应用还极少)。

国密是国家商用密码标准的简称。商用密码是指对不涉及国家秘密内容的信息进行加密保护或者安全认证。公民、法人和其他组织可以依法使用商用密码保护网络与信息安全。所有的国密相关标准都可以在这个站点查询到:http://www.gmbz.org.cn/main/bzlb.html。国密算法用SM(“商密”二字的拼音头字母组合)作为前缀标识,比如上面提到的SM2、SM3、SM4、SM9等等。

我们较为熟悉的是像RSA、AES这样的国际标准常用密码算法。初次看到SM2、SM3等算法名字的童鞋可能会有点懵,这些算法是什么密码算法,用在哪里?SM系列密码算法有很多,我们不能一一说明,我们重点来看看与安全传输层建立与认证相关的算法:

  • SM2:椭圆曲线公钥密码算法

SM2是用在公钥基础设施(PKI)领域的椭圆曲线公钥密码算法,与大名鼎鼎的RSA算法一样,是一种用于非对称加密的算法。该算法包括3部分:数字签名算法、密钥协商算法和加密/解密算法。该算法推荐使用素域为256比特的椭圆曲线。与RSA公钥密码算法相比,SM2椭圆曲线公钥密码算法具有安全性高、密钥短、速度快等优势。256比特的SM2椭圆曲线公钥密码算法密码强度已超过RSA-2048。SM2椭圆曲线公钥密码算法使用的密钥长度通常为192~256比特,而RSA公钥密码算法通常需要1024~2048比特。在同等安全强度下,SM2椭圆曲线公钥密码算法在用私钥签名时,速度远超RSA公钥密码算法。

SM2椭圆曲线公钥密码算法广泛应用于电子政务、移动办公、电子商务、移动支付、电子证书等领域。在公钥基础设施(PKI)领域,基于SM2椭圆曲线公钥密码算法的数字证书应用最具有代表性。

  • SM3:密码杂凑算法

SM3实质就是一种密码散列函数标准,再简单地说就是Hash函数。和我们熟悉的Hash散列算法SHA-1、SHA256等一样,SM3也主要用于数字签名及验证、消息认证码(MAC)生成及验证、随机数生成等领域。

SM3密码杂凑算法消息分组长度为512比特,输出摘要长度为256比特。SM3密码杂凑算法在MD(Message Digest)结构的基础上,新增了16步全异或操作、消息双字介入、加速雪崩效应的P置换等多种设计技术,能够有效避免高概率的局部碰撞,有效抵抗强碰撞性的差分攻击、弱碰撞性的线性攻击和比特追踪攻击等密码攻击方法。SM3密码杂凑算法能够有效抵抗目前已知的攻击方法,具有较高的安全冗余,在安全级别上与SHA256相当。

  • SM4:分组密码算法

SM4分组密码算法广泛应用于有对称加解密需求的应用系统和产品,与我们熟知的AES对称加密算法具有相同用途。

SM4算法的分组长度为128比特,密钥长度为128比特,加密算法和密钥扩展算法都采用32轮非线性迭代结构,解密算法与加密算法相同,只是轮密钥的使用顺序相反,解密轮密钥是加密轮密钥的逆序。轮变换使用的模块包括异或运算、8比特输入8比特输出的S盒,以及一个32比特输入的线性置换。

在密码指标性能方面,SM4分组密码算法的S盒设计已达到欧美分组密码标准算法的水平,具有较高的安全特性。线性置换的分支数达到了最优,可以抵抗差分攻击、线性攻击、代数攻击等。它具有算法速度快、实现效率高、安全性好等优点,主要用于保护数据的机密性。

除了密码算法之外,国家密码管理局还颁布一系列周边标准,比如基于国密的SSL传输层安全协议,以下简称为国密SSL

最初的国密SSL是作为密码行业标准存在的,没有独立的协议标准定义,而是定义在SSL LPN产品的技术规范里,即《GM/T 0024-2014 SSL VPN技术规范》

后来,国密SSL从密码行业标准上升到了独立的国家标准,也就是《GB/T 38636-2020 信息安全技术 传输层密码协议(TLCP)》,新版标准基本兼容《GM/T 0024-2014 SSL VPN技术规范》,主要变化是增加了GCM的密码套件:ECC_SM4_GCM_SM3和ECDHE_SM4_GCM_SM3以及去掉了行标《GM/T 0024-2014》中的涉及SM1和RSA的密码套件。

国密SSL是参考TLS 1.1制定的:

但“遗憾”的是国密SSL与TLS协议并不兼容,这就意味着现有的各个编程语言实现的TLS实现在不经改造的前提下,是无法支持国密SSL握手过程的。

此外,前面提到的TLS握手涉及到的证书都是RSA证书(前面证书内容中Public Key Algorithm: rsaEncryption),即用RSA算法生成的公钥,用RSA算法的CA私钥签过名的证书。另一端在验证证书时,也要用RSA算法公钥(CA公钥)验证证书。如果我们要支持基于SM2算法的证书体系,需要CA、参与通信的两端都要支持SM2算法。而如今支持SM2的CA少之又少。并且,从前面我们看到的Go标准库TLS实现内置的密码套件列表来看,我们也没有看到SM2等国密算法实现的踪影

不得不说,这是当前支持国密的一个“尴尬”。

那么我们要如何支持国密证书以及国密SSL呢?我们继续向下看。

五. 基于国密证书的tls身份认证

1. 使用openssl生成国密证书并验证是否可以成功进行tls握手

openssl是加解密领域“风向标”,openssl在1.1.1版本中加入对SM系列算法的支持:

大家可以通过下面命令查看你的openssl是否支持SM2椭圆曲线公钥密码算法:

$openssl ecparam -list_curves | grep SM2
  SM2       : SM2 curve over a 256 bit prime field

如果支持,我们就可以利用该算法制作国密证书了(openssl-sm2/certs下面)。

  • 使用SM2创建server端私钥
$openssl ecparam -genkey -name SM2 -out server-sm2.key
  • 创建server csr
$openssl req -new -out server-sm2.csr -key server-sm2.key
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

查看该csr:

$openssl req -in server-sm2.csr -noout -text
Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:5b:3f:7e:c7:36:43:9c:22:cf:68:34:73:7f:c2:
                    11:23:05:2d:e5:34:5f:29:30:11:c5:c4:f1:df:e2:
                    97:9d:5c:eb:6c:29:3e:d0:e3:a2:d4:6c:67:e4:4f:
                    42:90:70:a2:dc:db:a6:b4:fd:5d:53:b6:53:8e:fd:
                    a8:37:aa:5e:4b
                ASN1 OID: SM2
        Attributes:
            a0:00
    Signature Algorithm: ecdsa-with-SHA256
         30:45:02:21:00:be:4b:31:93:fb:6a:74:2f:0a:0d:8d:69:08:
         d1:ad:bf:b2:e8:02:c1:76:c5:50:01:f2:f9:c8:1e:6f:1f:4f:
         9b:02:20:2c:43:16:5f:a4:4b:fb:2d:26:13:04:e0:ef:27:d1:
         84:69:41:71:9a:aa:e8:29:1d:98:f8:0c:df:be:52:c6:9d

可以看到:

Public Key Algorithm: id-ecPublicKey
ASN1 OID: SM2

sm2算法是id-ecPublicKey算法的别名(alias):

$openssl list -public-key-algorithms

... ...
Name: sm2
    Alias for: id-ecPublicKey
  • 使用mkcert创建的ca来签发证书

该ca是使用RSA算法创建的。我们用它签发sm2证书。

$openssl x509 -req -in server-sm2.csr -CA ~/.local/share/mkcert/rootCA.pem -CAkey ~/.local/share/mkcert/rootCA-key.pem -CAcreateserial -out server-sm2-signed-by-rsa-ca.pem -days 5000
Signature ok
subject=C = AU, ST = Some-State, O = Internet Widgits Pty Ltd
Getting CA Private Key

查看生成的server端证书:

$openssl x509 -in server-sm2-signed-by-rsa-ca.pem -noout -text
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number:
            5c:9c:ac:2f:03:8e:4e:72:fd:41:8a:c5:eb:8e:d4:c0:fc:0f:8a:4b
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: O = mkcert development CA, OU = tonybai@tonybai, CN = mkcert tonybai@tonybai
        Validity
            Not Before: Jul 11 06:23:28 2022 GMT
            Not After : Mar 19 06:23:28 2036 GMT
        Subject: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:5b:3f:7e:c7:36:43:9c:22:cf:68:34:73:7f:c2:
                    11:23:05:2d:e5:34:5f:29:30:11:c5:c4:f1:df:e2:
                    97:9d:5c:eb:6c:29:3e:d0:e3:a2:d4:6c:67:e4:4f:
                    42:90:70:a2:dc:db:a6:b4:fd:5d:53:b6:53:8e:fd:
                    a8:37:aa:5e:4b
                ASN1 OID: SM2
    Signature Algorithm: sha256WithRSAEncryption
         2b:67:c0:12:41:ad:da:2a:2f:9f:89:81:f1:ef:4a:4b:6d:66:
         e8:93:62:e0:68:d4:5b:0e:8a:83:2b:4d:77:36:d1:8e:f2:d6:
         92:b0:7f:db:12:78:49:ac:c4:80:2b:ca:c8:70:91:c3:2f:31:
         8d:5d:97:27:60:77:95:e6:61:7c:62:c4:f5:0c:ce:90:43:7d:
         0c:f6:4e:8d:62:f3:67:08:4b:7e:5e:ad:0b:11:13:13:30:ec:
         d2:fc:78:ae:77:ca:97:f1:eb:fd:a3:5d:0f:58:70:a0:b3:2a:
         6e:91:eb:81:37:6f:54:a9:56:9b:11:3c:4e:63:0b:a2:d7:d6:
         36:b4:7f:d2:90:c3:15:ab:9b:bf:86:98:bb:9a:1c:64:71:3b:
         92:4c:aa:89:d1:8b:03:35:34:ad:64:66:83:bc:0d:5f:38:ba:
         a0:07:82:92:1b:44:ef:72:c2:36:eb:38:84:ac:a1:d3:44:17:
         a8:7b:d5:64:f6:55:05:5f:3a:3b:b5:eb:1a:66:51:33:7a:76:
         ce:e3:cc:82:04:f2:28:70:90:3a:57:a5:db:32:08:47:f1:4d:
         81:33:87:dd:b6:dc:4f:4f:49:59:e2:ac:71:a4:2f:7e:08:14:
         b0:cd:96:2d:fb:3d:b8:f2:c5:db:de:b9:0c:fe:91:15:fb:b1:
         2e:df:23:6f:3e:26:2c:66:db:5e:e2:f6:f3:1f:23:2c:5c:70:
         1d:d1:2b:b2:6e:ae:87:c6:cd:53:44:23:b0:1d:8d:08:40:3c:
         02:87:81:1d:65:04:2a:b8:c6:f5:59:28:6a:ea:22:95:d3:e2:
         24:93:9e:6c:d6:d7:0a:25:5b:4e:4a:cf:43:4c:71:e2:1a:bf:
         26:de:27:14:38:ea:69:9c:a9:bf:12:3a:5b:65:33:4e:83:87:
         81:5e:85:2a:e3:62:c7:5d:0e:15:e7:35:06:35:45:69:db:0b:
         aa:c6:45:e4:74:93:aa:45:e8:6f:22:11:15:14:f1:5a:4e:0a:
         34:e2:74:eb:44:32

使用openssl的s_server和s_client命令验证是否可以握手成功:

$openssl s_server -tls1_2 -accept 14443 -key server-sm2.key -cert server-sm2-signed-by-rsa-ca.pem -debug -msg
Using default temp DH parameters
ACCEPT

$openssl s_client -connect 127.0.0.1:14443 -debug -msg -tls1_2

结果s_server报如下错:

... ...
>>> TLS 1.2, Alert [length 0002], fatal handshake_failure
    02 28
ERROR
139761999033664:error:1417A0C1:SSL routines:tls_post_process_client_hello:no shared cipher:../ssl/statem/statem_srvr.c:2283:
shutting down SSL
CONNECTION CLOSED

可以看到openssl虽然可以生成sm2公钥证书,但在tls 1.2协议下无法成功实现tls握手。

2. 使用gmssl进行tls握手

openssl不支持,但国内的大神基于openssl1.1.0建立了gmssl分支,这就是gmssl工程。该工程为openssl增加了对国密算法以及gm ssl协议的各种支持。接下来我们就来试试用gmssl是否可以实现基于sm2证书的tls握手成功。

gmssl工程感觉还不够成熟,安装和运行过程有一些“坑”,这里简要说说。

  • 安装gmssl
$wget -c https://github.com/guanzhi/GmSSL/archive/master.zip
$unzip master.zip
$cd master

注意在执行其他config命令之前,先在Configure文件和test/build.info这个文件中, 把

use if $^O ne "VMS", 'File::Glob' => qw/glob/;

改成:

use if $^O ne "VMS", 'File::Glob' => qw/:glob/;

否则会报下面错误:

"glob" is not exported by the File::Glob module
Can't continue after import errors at ./Configure line 18.

接下来执行下面命令生成Makefile并构建:

$./config
$make

编译后的文件在apps/gmssl,我将其cp到项目根目录下。执行gmssl:

$./gmssl
./gmssl: symbol lookup error: ./gmssl: undefined symbol: BIO_debug_callback, version OPENSSL_1_1_0d

gmssl报错了!原因是加载器加载gmssl依赖的动态共享库时选择了系统openssl的相关库了:

$ldd gmssl
    linux-vdso.so.1 (0x00007ffe9cc5b000)
    libssl.so.1.1 => /lib/x86_64-linux-gnu/libssl.so.1.1 (0x00007fa3ca550000)
    libcrypto.so.1.1 => /lib/x86_64-linux-gnu/libcrypto.so.1.1 (0x00007fa3ca27a000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fa3ca257000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa3ca065000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fa3ca05f000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fa3ca6ae000)

我们要在load时链接gmssl自己的库,需要修改一下LD_LIBRARY_PATH环境变量(这样做会导致openssl执行失败,建议不要放在全局环境变量配置中,可让其仅在某些窗口中生效):

$export LD_LIBRARY_PATH=/home/tonybai/.bin/gmssl/GmSSL-master:$LD_LIBRARY_PATH

除此之外,我们还需要做一个操作,那就是在/usr/local/ssl下放置一份openssl.cnf文件(可以从/usr/lib/ssl/openssl.cnf拷贝(openssl version -a,查看OPENSSLDIR)),否则gmssl在执行“gmssl s_server…”时会报如下错误:

Can't open /usr/local/ssl/openssl.cnf for reading, No such file or directory
139679439200896:error:02001002:system library:fopen:No such file or directory:crypto/bio/bss_file.c:74:fopen('/usr/local/ssl/openssl.cnf','r')
139679439200896:error:2006D080:BIO routines:BIO_new_file:no such file:crypto/bio/bss_file.c:81:

这里gmssl版本如下:

$gmssl version
GmSSL 2.5.4 - OpenSSL 1.1.0d  19 Jun 2019

好了,下面我们就来使用gmssl试试我们制作的sm2证书是否可以顺利完成tls握手。

// 服务端
$gmssl s_server  -accept 14443 -key server-sm2.key -cert server-sm2-signed-by-rsa-ca.pem -debug -msg -tls1_2

// 客户端
$gmssl s_client -connect 127.0.0.1:14443 -debug -msg -tls1_2 -verifyCAfile /home/tonybai/.local/share/mkcert/rootCA.pem

---
SSL handshake has read 1209 bytes and written 310 bytes
Verification: OK
---
New, TLSv1.2, Cipher is ECDHE-SM2-WITH-SMS4-GCM-SM3
Server public key is 256 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-SM2-WITH-SMS4-GCM-SM3
    Session-ID: 53B8799C3A6F3752C634F764EB6B136BDFD39CEB0C2E28E7DD98D86F9FF4F333
    Session-ID-ctx:
    Master-Key: 6A50D31E3AEDDDF3FC608277087FB0DAACCC791DB296142ED37DE28E0DDA56FF1BB64431B66A76C468129E00F696338D
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket lifetime hint: 7200 (seconds)
    TLS session ticket:
    0000 - ee d3 08 4d 21 14 dc c8-40 8c d0 c4 31 f9 16 bc   ...M!...@...1...
    0010 - 85 f9 a2 8c f4 ba cf 90-4d 38 28 03 78 b0 4a 27   ........M8(.x.J'
    0020 - 17 c4 22 df 48 ea 8c 00-5a 92 0f ba eb 8a 1a dc   ..".H...Z.......
    0030 - b3 3d b4 15 ee df fc d0-66 59 5c c2 23 9e a4 4f   .=......fY\.#..O
    0040 - e0 77 54 b1 18 af 73 b0-b4 6a a7 c7 c7 d3 a4 a4   .wT...s..j......
    0050 - 8f 49 ff c7 bc 47 b5 19-09 21 4c db 71 76 d9 a5   .I...G...!L.qv..
    0060 - 49 0b c9 5d 09 b2 da b9-cc ec 04 5a 90 27 07 5f   I..].......Z.'._
    0070 - 2b f2 55 5c f4 69 01 32-90 f5 3a 19 b5 47 84 4c   +.U\.i.2..:..G.L
    0080 - 1c 64 66 63 f3 01 ab fe-b1 70 f7 98 b5 cc 23 8e   .dfc.....p....#.
    0090 - aa f4 1d 8a 79 5e 79 b7-04 f6 69 ed 62 d9 c7 ae   ....y^y...i.b...

    Start Time: 1657529930
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
    Extended master secret: yes

从客户端的输出来看,在明确ca证书位置的情况下(使用-verifyCAfile),可以正确验证server端发来的sm2证书(见“Verify return code: 0 (ok)”)。

六. 使用Go实现tls/tlcp自适应双向认证

gmssl为我们展示了一条支持国密的路径,即基于已有的开源项目的实现进行改造。Go标准库并不支持国密,因此在Go社区借鉴标准库中crypto的中算法以及tls包的结构,实现了对sm系列算法以及国密ssl的支持,tjfoc/gmsm就是其中之一。

注:gmssl也提供了Go API接口,底层通过cgo调用gmssl C代码实现。

gmsm不仅提供了国密算法的相关实现,还实现了tls与tclp协议的自适应支持。在这一小节,我们就用gmsm来演示一个tls/tlcp自适应双向认证的例子。

1. 准备SM2国密公钥证书

按照gmsm自适应tls/tlcp实现的要求,我们需要先准备一堆证书(tlcp与tls不同,其加密与签名是由两个证书分别完成的,而不仅仅是tls的一个证书),包括:

  • rsa: 基于rsa的CA证书、server证书和client证书
  • gm: 基于gm的CA证书、server签名(sign)和加密(enc)证书、client端验证(auth)证书。

考虑到mkcert不支持国密,这里我们切换到用gmssl来创建这些证书。我将创建证书的命令集中在两个shell脚本中:gen_rsa_cert.sh和gen_gm_cert.sh,前者用于创建基于RSA的各种证书,后者则是创建基于国密的各种证书。这两个脚本的源码如下:

  • gen_rsa_cert.sh
// gmssl-examples/gmsm-tls-and-tlcp/certs/gen_rsa_cert.sh

#!/bin/bash

## RSA Certs

### CA
gmssl genpkey -out ca-rsa-key.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048
gmssl req -x509 -new -nodes -key ca-rsa-key.pem -subj "/CN=myca.com" -days 5000 -out ca-rsa-cert.pem 

### server key and cert
gmssl genpkey -out server-rsa-key.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048
gmssl req -new -key server-rsa-key.pem -subj "/CN=example.com" -out server-rsa.csr
gmssl x509 -req -in server-rsa.csr -CA ca-rsa-cert.pem -CAkey ca-rsa-key.pem -CAcreateserial -out server-rsa-cert.pem -days 5000  -extfile ./server.cnf -extensions ext
gmssl verify -CAfile ca-rsa-cert.pem server-rsa-cert.pem

### client key and cert
gmssl genpkey -out client-rsa-key.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048
gmssl req -new -key client-rsa-key.pem -subj "/CN=client1.com" -out client-rsa.csr
gmssl x509 -req -in client-rsa.csr -CA ca-rsa-cert.pem -CAkey ca-rsa-key.pem -CAcreateserial -out client-rsa-cert.pem -days 5000 -extfile ./client.cnf -extensions ext
gmssl verify -CAfile ca-rsa-cert.pem client-rsa-cert.pem
  • gen_gm_cert.sh
#!/bin/bash

## SM CA

gmssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:sm2p256v1 -out ca-gm-key.pem
gmssl req -x509 -new -nodes -key ca-gm-key.pem -subj "/CN=myca.com" -days 5000 -out ca-gm-cert.pem

### server: sign key and cert

gmssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:sm2p256v1 -out server-gm-sign-key.pem
gmssl req -new -key server-gm-sign-key.pem -subj "/CN=example.com" -out server-gm-sign.csr
gmssl x509 -req -in server-gm-sign.csr -CA ca-gm-cert.pem -CAkey ca-gm-key.pem -CAcreateserial -out server-gm-sign-cert.pem -days 5000 -extfile ./server.cnf -extensions ext

gmssl verify -CAfile ca-gm-cert.pem server-gm-sign-cert.pem

### server: enc key and cer

gmssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:sm2p256v1 -out server-gm-enc-key.pem
gmssl req -new -key server-gm-enc-key.pem -subj "/CN=example.com" -out server-gm-enc.csr
gmssl x509 -req -in server-gm-enc.csr -CA ca-gm-cert.pem -CAkey ca-gm-key.pem -CAcreateserial -out server-gm-enc-cert.pem -days 5000 -extfile ./server.cnf -extensions ext

gmssl verify -CAfile ca-gm-cert.pem server-gm-enc-cert.pem

### client: auth key and cert

gmssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:sm2p256v1 -out client-gm-auth-key.pem
gmssl req -new -key client-gm-auth-key.pem -subj "/CN=client1.com" -out client-gm-auth.csr
gmssl x509 -req -in client-gm-auth.csr -CA ca-gm-cert.pem -CAkey ca-gm-key.pem -CAcreateserial -out client-gm-auth-cert.pem -days 5000 -extfile ./client.cnf -extensions ext

gmssl verify -CAfile ca-gm-cert.pem client-gm-auth-cert.pem

关于上面两个脚本,有几点说明一下:

  1. 我们建立了两个CA,一个基于RSA,一个基于国密算法;这两个CA分别用于签发基于RSA的证书与基于国密的证书;
  2. 在生成证书中我们用到了x509证书的扩展属性subjectAltName、extendedKeyUsage和keyUsage。

如果不使用subjectAltName扩展属性,Go语言的x509校验会报如下错误(在Go 1.18及后续版本中,即便设置GODEBUG=x509ignoreCN=0也不行):

certificate relies on legacy Common Name field, use SANs instead

同样Go也会对keyUsage做严格校验,如果是用来签名的证书中keyUsage不包含digitalSignature等,握手时也会报错:

tls: the keyusage of cert[0] does not exist or is not for KeyUsageDigitalSignature

server.cnf与client.cnf的内容如下:

// server.cnf
[req]
prompt = no
distinguished_name = dn
req_extensions = ext
input_password = 

[dn]
CN = example.com
emailAddress = webmaster@example.com
O = hello Ltd
L = Beijing
C = CN

[ext]
subjectAltName = DNS:example.com
extendedKeyUsage = clientAuth,serverAuth
keyUsage = critical,digitalSignature,keyEncipherment

// client.cnf

[req]
prompt = no
distinguished_name = dn
req_extensions = ext
input_password = 

[dn]
CN = client1.com
emailAddress = webmaster@client1.com
O = hello Ltd
L = Beijing
C = CN

[ext]
subjectAltName = DNS:client1.com
extendedKeyUsage = clientAuth
keyUsage = critical,digitalSignature,keyEncipherment

执行bash gen_rsa_cert.sh和bash gen_gm_cert.sh生成所有示例需要的证书:

$ls *.pem|grep -v key
ca-gm-cert.pem
ca-rsa-cert.pem
client-gm-auth-cert.pem
client-rsa-cert.pem
server-gm-enc-cert.pem
server-gm-sign-cert.pem
server-rsa-cert.pem

2. 支持tls与tlcp自适应的server

下面是支持tls与tlcp自适应的server的源码:

// gmssl-examples/gmsm-tls-and-tlcp/server/server.go
const (
    rsaCertPath     = "certs/server-rsa-cert.pem"
    rsaKeyPath      = "certs/server-rsa-key.pem"
    sm2SignCertPath = "certs/server-gm-sign-cert.pem"
    sm2SignKeyPath  = "certs/server-gm-sign-key.pem"
    sm2EncCertPath  = "certs/server-gm-enc-cert.pem"
    sm2EncKeyPath   = "certs/server-gm-enc-key.pem"
)

func main() {
    pool := x509.NewCertPool()
    rsaCACertPath := "./certs/ca-rsa-cert.pem"
    rsaCACrt, err := ioutil.ReadFile(rsaCACertPath)
    if err != nil {
        fmt.Println("read rsa ca err:", err)
        return
    }
    gmCACertPath := "./certs/ca-gm-cert.pem"
    gmCACrt, err := ioutil.ReadFile(gmCACertPath)
    if err != nil {
        fmt.Println("read gm ca err:", err)
        return
    }
    pool.AppendCertsFromPEM(rsaCACrt)
    pool.AppendCertsFromPEM(gmCACrt)

    rsaKeypair, err := tls.LoadX509KeyPair(rsaCertPath, rsaKeyPath)
    if err != nil {
        fmt.Println("load rsa x509 keypair error:", err)
        return
    }
    sigCert, err := tls.LoadX509KeyPair(sm2SignCertPath, sm2SignKeyPath)
    if err != nil {
        fmt.Println("load x509 gm sign keypair error:", err)
        return
    }
    encCert, err := tls.LoadX509KeyPair(sm2EncCertPath, sm2EncKeyPath)
    if err != nil {
        fmt.Println("load x509 gm enc keypair error:", err)
        return
    }

    cfg, err := tls.NewBasicAutoSwitchConfig(&sigCert, &encCert, &rsaKeypair)
    if err != nil {
        fmt.Println("load basic config error:", err)
        return
    }

    cfg.MaxVersion = tls.VersionTLS12
    cfg.ClientAuth = tls.RequireAndVerifyClientCert
    cfg.ClientCAs = pool

    listener, err := tls.Listen("tcp", ":18000", cfg)
    if err != nil {
        fmt.Println("listen error:", err)
        return
    }

    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("accept error:", err)
            return
        }
        fmt.Println("accept connection:", conn.RemoteAddr())
        go func() {
            for {
                // echo "request"
                var b = make([]byte, 16)
                _, err := conn.Read(b)
                if err != nil {
                    fmt.Println("connection read error:", err)
                    conn.Close()
                    return
                }

                fmt.Println(string(b))
                _, err = conn.Write(b)
                if err != nil {
                    fmt.Println("connection write error:", err)
                    return
                }
            }
        }()
    }
}

说明一下:

  • 这里的tls包是并非标准库crypto/tls包,而是github.com/tjfoc/gmsm/gmtls。
  • 由于要自适应tls/tlcp,我们加载了两个CA证书,一个是基于RSA创建的CA证书,一个是基于gm创建的CA证书,用于分别对tls协议和tlcp协议的客户端身份进行验证;
  • 服务端加载了用于tls连接的RSA的server证书:rsaCertPath,同时也加载了用于tlcp连接的server端双证书:sm2SignCertPath和sm2EncCertPath。

3. tls client

用于该示例的tls client与前面的echoclient十分类似,只不过加载的证书从mkcert生成的cert.pem改为certs/client-rsa-cert.pem,CA证书使用了我们刚刚生成的./certs/ca-rsa-cert.pem。

其他部分没有变化。这里就不罗列源码了,大家可以自行阅读gmssl-examples/gmsm-tls-and-tlcp/tlsclient/client.go

4. tlcp client

和tls client相比,我们只是将CA换为./certs/ca-gm-cert.pem,加载的client证书换成了certs/client-gm-auth-cert.pem,其他部分没有变化。这里也不罗列源码了,大家可以自行阅读gmssl-examples/gmsm-tls-and-tlcp/tlcpclient/client.go

5. 验证tls/tlcp自适应双向认证

通过make命令可以一键构建出上述的server、tlsclient和tlcpclient。

启动server:

$./echoserver

启动tlsclient,验证tls双向认证:

$./echo_tls_client
connect ok
hello, tls
hello, tls
... ...

如果看到上面的tls client输出,说明tls连接建立和双向验证ok。

我们再来启动tlcp client,验证tlcp双向认证:

$./echo_tlcp_client
connect ok
reply: h
reply:
reply: e
reply: llo, tlcp
reply: h
reply:
reply: e
reply: llo, tlcp
reply: h
reply:
reply: e
reply: llo, tlcp
... ..

我们看到虽然tlcp连接建立成功并成功完成双向认证,但是基于已建立的tlcp的读写操作似乎并不想tls client那样“工整”,对应着server那端的输出如下:

accept connection: 127.0.0.1:58088
h
ello, tlcp
h
ello, tlcp
h
ello, tlcp
h
ello, tlcp
h
ello, tlcp
h
ello, tlcp
h
ello, tlcp
h
ello, tlcp
h
ello, tlcp

虽然两段的数据都是完整的,没有丢失,但发送与接收的“效率”大幅下降,client端发出的一个“hello, tlcp”数据似乎是被分为两次发送出去的。而服务端给客户端的Reply更是分成了“四段”发送的,目前还没有调查为何会出现这种情况,也许与tjfoc/gmsm的实现有关。

注:实测:tjfoc/gmsm尚不支持在tls协议握手时使用rsa CA证书签发的采用gm算法生成的sm2证书,可以参见gmssl-examples/gmsm-tls-and-tlcp/server_gm和tlsclient_gm。

七. 小结

国密是中国密码标准,和国际密码标准相比,有一定的后发优势,但由于在国际上应用很少,其安全性虽然得到了形式验证,但似乎尚未得到实践中的大规模考验。基于国密的tlcp协议由于与tls不兼容,也导致其在应用上受到了极大的限制。

虽然有gmssl、有像tjfoc/gmsm这样的项目,但总体感觉国密在参考实现方面还不够成熟,生态还很欠缺,国家密码局在推广国密方面往往更多从法规层面。各个厂家往往都是因甲方需要国密而去满足要求,并没有原生推动国密的动力(譬如我们^_^)。

因此,国密任重道远啊。

本文内容仅供参考,可能有理解不正确和代码错误的地方,欢迎指正。

文中示例代码可以在这里下载。

八. 参考资料


“Gopher部落”知识星球旨在打造一个精品Go学习和进阶社群!高品质首发Go技术文章,“三天”首发阅读权,每年两期Go语言发展现状分析,每天提前1小时阅读到新鲜的Gopher日报,网课、技术专栏、图书内容前瞻,六小时内必答保证等满足你关于Go语言生态的所有需求!2022年,Gopher部落全面改版,将持续分享Go语言与Go应用领域的知识、技巧与实践,并增加诸多互动形式。欢迎大家加入!

img{512x368}
img{512x368}

img{512x368}
img{512x368}

我爱发短信:企业级短信平台定制开发专家 https://tonybai.com/。smspush : 可部署在企业内部的定制化短信平台,三网覆盖,不惧大并发接入,可定制扩展; 短信内容你来定,不再受约束, 接口丰富,支持长短信,签名可选。2020年4月8日,中国三大电信运营商联合发布《5G消息白皮书》,51短信平台也会全新升级到“51商用消息平台”,全面支持5G RCS消息。

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻)归档仓库 – https://github.com/bigwhite/gopherdaily

我的联系方式:

  • 微博:https://weibo.com/bigwhite20xx
  • 博客:tonybai.com
  • github: https://github.com/bigwhite

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

究竟是什么让Go语言成为恶意软件作者的最爱

2020年5月份,Go语言之父Rob Pike接受了evrone.com的专访。当Rob Pike老爷子被问及多年来他看到过最奇怪、最有创意或有趣的Go用法或最让他惊讶的是什么时,老爷子是这么回答的:

Rob:最大的惊喜是当我们得知Go被用于编写恶意软件时。您无法控制谁将使用您的作品或他们将如何使用它。

近期安全技术公司Intezer发布了一份名为《Year of the Gopher, A 2020 Go Malware Round-Up》的报告,该报告称在过去几年中,安全人员发现的用Go编写的新恶意软件几乎增加了2000%,这一标题迅速引爆程序员社区,有人唾弃Go踏入“歧途”,也有人膜拜Go的niubility:能被黑客看中和使用的都是精华!

那么究竟是什么让黑客们这么青睐Go并用之去编写恶意软件呢?估计但那份几十页的报告没几个人会完整的读一遍,本文我们就结合报告的内容(分类、整理、摘录)做一些探究。

1. Go语言的简介

报告首先简单介绍了Go的前世今生

Go是一种开源的编程语言,由Robert Griesemer、Rob Pike和Ken Thompson于2007年在Google开发。它于2009年11月向公众发布。开发新语言的动机来自于使用当前编程语言(当时三巨头都是用C++)的挫折感。由于CPU不再通过增加时钟周期的数量来提高速度。相反,更多的速度开始通过添加更多的CPU核并允许更多的并行执行来获得。这种硬件上的进化并没有很好地反映在通用编程语言中。虽然C、C++和Java等语言提供了在多核上并行执行事务的功能,但它们为程序员提供的帮助却很少,无法高效、安全地完成这项工作。

Google的程序员们于是开始设计一种新的编程语言,为方便和安全的使用并发或并行提供“原生/一等公民地位”的支持。另一个目标则是要将解释型语言的编程便利性与静态类型和编译型语言的效率和安全性结合起来。另外在设计时,Google是将其用于Google基础设施运行的一部分网络服务中,因此对网络的支持也很重要。

为了提供在解释语言中编程的感觉,Go使用垃圾收集并处理所有的内存管理。所有的Go二进制文件都包含一个称为运行时的通用库,这导致Go二进制文件的大小比用C语言编写的类似的静态链接的程序要大。该库负责处理垃圾收集、执行线程的调度以及该语言的所有其他关键功能。虽然它被称为运行时,但比起Java运行时,它更像C语言的libc,它已经与二进制文件进行了静态编译。Go二进制文件被编译成本地机器代码,但也可以被编译成以JavaScript为运行时的WebAssembly。

Go 1.4版本及更早版本的编译器是用C语言实现的,但随着2015年1.5版本的发布,编译器完全用Go语言编写,并实现了自举。转为自举编译器后,给用户在交叉编译方面的体验带来了巨大的改善。之前使用基于C语言的编译器时,需要在编译代码的机器上安装一个针对目标操作系统和架构的C编译器。和针对不同目标的C代码进行交叉编译时的方式非常相似。从1.5版本开始,只需要向编译器指明它的编译目标架构,就可以实现对不同操作系统和架构的交叉编译。不需要针对目标的特殊编译器。Go可以通过不依赖主机上的库来执行例如syscalls(系统调用)。本来由libc提供的功能由Go的标准库提供和处理。这种方便的交叉编译有一个限制,那就是当Go程序需要通过其外函数接口(FFI)与C语言编写的库进行交互时。

新的功能和解决方案使得程序员在新项目中采用Go。2016年,TIOBE授予Go“年度最佳编程语言”,这是一个授予评分上升幅度最高的语言的奖项。随着软件开发者因其功能而开始采用Go,恶意软件作者也开始采用Go也就不足为奇了。

人们注意到使用Go开发的恶意软件增多是从2019年Palo Alto Networks公司发布的一份分析报告开始的。2019年7月,Palo Alto Networks公司的Unit 42发布了对当时发现的用Go编写的恶意软件的分析报告。研究发现,2017年至2019年期间,人们发现的Go恶意软件样本增加 了1944%,这量化了一个很容易发现的趋势。在2019年之前,发现用Go编写的恶意软件更多的是一种罕见的现象,而在2019年期间,这成为了一种日常现象。报告中分析的恶意软件中,大部分,92%的恶意软件针对Windows,而4.5%针对Linux,3.5%针对macOS。
人们观察到的另一点是,渗透测试(pen-testing)团队采用Go来开发他们的工具,这在Unit 42的研究中很突出。

最常见的恶意软件家族类型是开源或渗透测试后门。其次是coinminer(挖矿)、窃取者和僵尸网络。这篇报告涵盖了2020年期间活跃的用Go编写的已知恶意软件的活动。

2. 使用Go的嵌入文件功能实现恶意加载器

与其他语言产生的二进制文件相比,Go编译器产生的二进制文件相对较大。例如,一个Hello World二进制文件有1700多个函数。由于二进制文件中有这么多的常用代码,因此在寻找可疑代码时就像大海捞针一样。这可能是为什么恶意Go二进制文件有时不被 反病毒引擎检测到的原因之一。这导致一些威胁行为者在Go中开发加载器,并利用它们来提供其他较老的、易被检测到的恶意软件。这种技术可以降低被检出率,甚至有时会使恶意软件完全无法被检测到。在Go二进制文件中嵌入其他二进制文件相对容易。有很多开源库已经解决了这个问题。下面是其中的一些列表:

  • https://github.com/gobuffalo/packr
  • https://github.com/rakyll/statik
  • https://github.com/GeertJohan/go.rice
  • https://github.com/UnnoTed/fileb0x
  • https://github.com/mjibson/esc
  • https://github.com/kevinburke/go-bindata
  • https://github.com/lu4p/binclude
  • https://github.com/omeid/go-resources
  • https://github.com/pyros2097/go-embed
  • https://github.com/wlbr/mule
  • https://github.com/miscing/embed
  • https://github.com/kyioptr/gassets

上述包的大部分的设计都是为了允许嵌入网络服务的静态资源文件(asset),但使用案例并不限于此。嵌入文件的功能受到了广泛的好评,以至于今年2020年早些时候有人建议将该功能直接添加到Go编译器中。该建议已被接受,并已与2021年2月发布的Go 1.16版本一起发布了。从这个角度来看,Go 1.16版本加入嵌入文件功能,颇有些“助纣为虐”之嫌^_^。

3. 使用Go标准库强大的加密库和便捷的跨主机交叉编译特性实现恶意加密器和勒索软件

Go的标准库提供了一套非常强大的加密库,允许开发者在不需要使用任何第三方库的情况下,在应用中加入加密功能。

一个开源的加密加载器是Go shellcode LoaDer。它用AES对有效载荷进行加密。它对有效载荷进行解密,并在执行之前使用ZwProtectVirtualMemory将解密缓冲区标记为读取/执行。

我们还观察到威胁行为者编写自己的加密器和加载器。例如,我们看到一个名为gocrypter的加载器被用于加密商品恶意软件;大多数是RAT(Remote Access Trojans,远程访问木马)和键盘记录器。有效载荷已经用AES加密,并作为base64编码的blob存储在二进制内部。加密器将其解码成字节,并在写入磁盘和执行之前进行解密。

在2020年仍有一些活动的勒索软件,比如:RobbinHood。RobbinHood在2019年春季被发现,当巴尔的摩市被发现受到该勒索软件攻击时,得到了很多媒体的关注。Sophos在2月份发布了一份报告,详细介绍了该威胁行为者的一些演变过程。通过利用技嘉公司的一个脆弱的驱动程序,威胁行为者开始加载一个未签名的驱动程序。一旦驱动程序被加载,它将杀死进程和篡改保护软件,以确保勒索软件可以在不被中断的情况下加密硬盘驱动器的其余部分。但在2020年11月,仍有新的样本被发现,但勒索说明没有改变。11月的一个样本的PDB字符串为C:/Users/User/go/src/Robbinhood7,这表明根据恶意软件作者的说法,它可能是第7个版本的勒索软件。

另一个用Go编写的、仍然活跃的老牌勒索软件是Snatch。Snatch是在2018年12月被发现的,到现在似乎还在使用。该勒索软件由Snatch Team使用,他们通过远程访问服务(例如RDP)瞄准企业环境。一旦进入网络,该组织就会尝试在所有机器上部署勒索软件, 并对文件进行加密。该勒索软件在加密文件时有一个有趣的技术,该技术在2019年10月被引入到勒索软件中。该勒索软件将自己安装为一项服务,即使Windows启动到安全模式,也可以启动。在此之后,勒索软件将Windows重新启动到安全模式,允许它加密硬盘上的所有文件,而不会被安装的任何潜在的安全保护软件阻止。

Nefilim是一款勒索软件,最早出现在2020年3月。它是另一款名为Nemty的勒索软件的前身。最初的版本是用C++编写的,但在7月,该恶意软件用Go重新编写。除了加密受害者机器上的文件外,Nefilim背后的威胁行为者还窃取受害者的数据,并用于勒索。

由于Go提供了一种针对不同架构和操作系统交叉编译二进do制文件的简单方法,因此它被用于RaaS(Ransomware as a Service)勒索软件并不奇怪。它允许威胁行为者使用单一的代码库,以极低的工作量制作针对不同操作系统的二进制文件。Go已经被用于RaaS。在2020年的春天,一个新的RaaS被宣布,名为Smaug。Smaug是一个相对简单的勒索软件,但它为Windows、Linux和macOS提供”用户”的勒索软件服务。它可以在”企业”模式下运行,即所有机器使用一个密钥,或者每台机器模式下使用一个密钥。

Go可以为其他操作系统和架构制作二进制文件,这使得威胁行为者可以轻松地针对不同类型的设备,例如,嵌入式系统。在2019年夏天,我们发现了QNAPCrypt,也就是eCh0raix,这是一款针对QNAP NAS设备的勒索软件。后来,它还被用来针对Synology NAS设备。2020年,又发现了一款针对QNAP设备的新勒索软件。新的勒索软件被称为AgeLocker,因为它使用了开源的加密工具和库age

在2020年期间发现的其他用Go编写的勒索软件包括。1月发现的Betasup,2月发现的Sorena也就是HackForLife和Vash,3月发现的GoGoogle。

4. 使用Go优秀的网络协议栈实现开发RAT(远程访问木马)、恶意偷窃程序、恶意机器人和僵尸网络

Go的网络协议栈写得非常好,易于操作。Go已经成为云计算的编程语言之一,很多云原生应用都是用它编写的。例如,Docker、Kubernetes、InfluxDB、Traefik、Terraform、CockroachDB、Prometheus和Consul都是用Go编写的。这是有道理的,因为创建Go背后的原因之一正是要发明一种更好的语言,可以用来取代Google内部使用的C++网络服务。因此远程访问木马(RAT)是用Go编写的,这并不奇怪。毕竟,它们非常需要优良的网络服务功能。

在这一年中,既有新的RAT出现,也有老的RAT不断被使用。早在2020年8月,我们发现了一个Linux版本的Carbanak威胁行为体使用的后门。该样本使用2017年2月发布的Go 1.8版本编译器进行编译。同样的编译器版本和构建环境被用于2017年RSA报告的一部分的初始Windows样本。

Glupteba是一个自2011年以来一直存在的恶意软件,但在2019年9月,发现了一个用Go改写的新版本。在整个2020年,这个新版本出现的更为频繁。该恶意软件在感染机器时,会尝试安装一个root-kit。为了绕过Windows中防止安装内核驱动程序的保护措施,恶意软件利用了一个脆弱的VirtualBox驱动程序。恶意软件会安装该驱动程序,由于该驱动程序是经过签名的,所以Windows会允许安装,并使用它在Ring-0中执行代码,以禁用Kernel Patch Protection(KPP)。这种技术并不新鲜,它最早被APT组织Turla使用。除此之外,该恶意软件还试图通过利用EternalBlue在本地网络内进行传播。

Windows并不是唯一一个被用Go编写的RAT攻击的操作系统。2020年10月,Bitdefender发布了一个针对Linux的新RAT的发现。Bitdefender的研究人员认为,它可能与2019年的PowerGhost活动有关。该威胁行为体针对的是易受CVE-2019-2725影响的WebLogic服务器。该RAT似乎被作者命名为NiuB。该恶意软件由两个二进制文件组成,即主恶意软件和一个防护恶意软件。该恶意软件收集受感染机器的信息,并将其发送到C2服务器。它可以执行shell命令,下载并执行其他二进制文件。

2020年1月,FireEye发布了一份针对NetScaler设备的攻击报告。攻击是利用CVE-2019-19781漏洞。作为攻击的一部分,威胁行为者使用了一种新的恶意软件,以前从未见过。FireEye将该恶意软件命名为NOTROBIN。它是用Go编写的,并被编译成在*BSD上运行,这是NetScaler使用的底层操作系统。一个有趣的功能是,该恶意软件通过扫描新的NetScaler模板文件并将其删除来阻止其他恶意软件利用相同的漏洞,这些文件可能是作为利用尝试的一部分添加的。它在18634端口上打开一个UDP监听器,但忽略发送到它的数据。它基本上充当了一个mutex,以确保受感染的机器上只运行一个恶意软件的副本。

已经有一些用Go编写的窃取器。在2019年,Malwarebytes报告了一个名为CryptoStealer.Go的窃取器。它旨在窃取加密货币钱包和 存储在浏览器中的数据,如信用卡信息。

同样在2020年期间,发现了一个用Go编写的剪贴板窃取器。它似乎自2019年以来一直活跃。根据上传到VirusTotal的样本的文件名 ,该窃取器被伪装成黑客工具,表明它被用来针对其他威胁行为者。该恶意软件的设计很简单。它将自己安装在App/DataLocal/Support下,并隐藏文件或文件夹。它读取剪贴板并检查它是否看起来像加密货币地址。如果是,恶意软件就会用攻击者自己的比特币、莱特币、Monero或Ethereum钱包替换剪贴板内容。

该恶意软件中的比特币钱包地址自2018年秋季以来一直处于活跃状态。截至本文撰写时,它已经收到了534笔交易,价值近11BTC。

随着Go作为标准库的一部分支持许多网络协议,以及为不同架构编译二进制文件的便利性,越来越多的机器人用Go编写也就不足为奇了。另外,二进制文件包含了正常运行所需的一切,这也为代码作者提供了更多的保证,例如,它可以在不同的Linux发行版上运行。它不用担心机器上是否已经安装了库。因为它需要什么,就自带什么。还有很多第三方库,提供了访问其他服务的功能。

比如这里列出了一些机器人库,可以用来开发不同服务的机器人。

  • https://github.com/go-joe/joe
  • https://github.com/bot-api/telegram
  • https://github.com/shomali11/slacker
  • https://github.com/go-chat-bot/bot
  • https://github.com/frodsan/fbot
  • https://github.com/go-telegram-bot-api/telegram-bot-api
  • https://github.com/tucnak/telebot

随着开源机器人库的出现,它们被恶意软件作者滥用的情况并不少见。IRCFlu就是一个例子。IRCFlu是一个托管在GitHub上的IRC机器人。该机器人提供了在托管机器人的机器上执行任意代码的功能,这使得威胁行为者可以利用这个机器人远程控制多台受感染的机器。

除了开源项目被滥用外,2020年还出现了老牌知名僵尸网络的攻击行为。被称为ddg的僵尸网络是由Netlab在360首次报道的。他们在2017年10月检测到该僵尸网络对托管OrientDB的服务器的攻击。该僵尸网络的目标是安装Monero矿机。2020年,该僵尸网络进行了更新,通过增加一个p2p网络支持的C2基础设施,使其更有弹性地抵御击杀。混合的p2p网络基础设施允许威胁行为者在正常的C2服务器瘫痪时保持对机器人的控制。

另一个仍然活跃的老僵尸网络是StealthWorker,也被称为GoBrut。StealthWorker是Malwarebytes在2019年2月首次报道的。它是一个以Stealth Bomber为名在暗网论坛上销售的僵尸,用于通过凭证式蛮力攻击获得网络服务的访问权限。

僵尸网络r2r2是另一个通过蛮横强迫凭证传播的僵尸。它最早是在2018年被发现的。它随机生成IP地址,并试图通过弱凭证访问运行SSH的服务。一旦它获得了一个立足点,它就会在机器上安装一个密码器。该僵尸的功能非常有限,它由不到200百行的代码组成。

其他僵尸网络也在不断进化,以增加其潜在的目标。在2020年,Orthrus,也被称为Golang,演变为也针对Windows服务器。该僵尸是Antiy在2019年6月首次报道的。它主要针对未受保护或凭证薄弱的Redis服务器。一旦它获得远程代码执行,它就会安装一套二进制文件。一个是针对其他易受攻击服务的扫描器,一个看门狗服务和一个密码器。扫描器试图破坏其他有已知漏洞的网络服务。例如,Weblogic,Elasticsearch和Drupal是目标。在2020年,该恶意软件还增加了针对微软SQL服务器的目标。它试图通过强行获取凭证来获得访问权。该恶意软件包括一个近3000个密码的列表,它只针对SQL服务器使用。

12月,我们发现了另一个跨操作系统的挖掘机器人,我们称之为XMRig Miner Dropper。它的目标是运行MySQL、Tomcat和Jenkins的服务器以及凭证较弱或脆弱的WebLogic。根据底层操作系统的不同,该机器人提供了一个用于执行shell脚本或PowerShell脚本的有效载荷。一旦它入侵机器,它就会安装一个密码器,并试图利用其他服务器。

2016年9月,Mirai的源代码被发布。这导致许多新的僵尸网络从Mirai源代码中衍生出来。虽然该僵尸代码是用C++编写的,但该代码的发布为其他恶意软件作者用不同语言编写类似的僵尸提供了蓝本。2020年1月,Bitdefender发布了一份报告,介绍了一个用Go编写的受Mirai启发的新僵尸网络,他们将其命名为LiquorBot。该僵尸网络本质上是Mirai在Go中的重新实现,目标是运行在ARM(32位和64位)、x86(32位和64位)和MIPS上的Linux设备。该僵尸通过强行获取SSH证书和利用路由器的已知漏洞进行传播。一旦它获得了设备的访问权限,它就会试图感染其他人,并且还安装了一个Monero密码器。

LiquorBot并不是唯一受Mirai启发的僵尸网络。4月,我们发现了Kaiji,这是一个通过SSH蛮横强迫来针对Linux服务器和物联网设备的僵尸网络。除了强行插入薄弱的凭证外,该僵尸还试图使用在受感染机器上发现的本地SSH密钥来传播到企业内的其他机器。与Mirai类似,Kaiji允许僵尸管理员对他们选择的任何基础设施发起DDoS攻击。攻击包括两个TCPFlood实现(一个带有原始套接字)、两个UDPFlood实现(一个带有原始套接字)、IPSpoof攻击、SYNACK攻击、SYN攻击和ACK攻击。

2020年6月,Kaiji将其目标方法扩大到包括暴露API套接字的服务器。该恶意软件开始在互联网上扫描端口2375暴露的主机。如果它找到了一个,它会尝试部署一个流氓Docker容器,并在容器中执行Kaiji。

Kaiji不是唯一一个针对暴露的Docker API的僵尸网络。2020年11月,NetLab 360报告发现了一种名为Blackrota的新恶意软件。Kinsing,也被称为h2Miner,已经被称为针对Docker API。2020年1月,阿里巴巴云的研究人员首次报道了Kinsing。该僵尸网络正在使用masscan寻找暴露Hadoop Yarn、Redis和Docker的机器。当它发现一台运行这些服务的服务器时,它会试图利用服务中的已知漏洞来进一步传播自己。5月,我们观察到Kinsing利用SaltStack的两个漏洞CVE-2020-11651和CVE-2020-11652进行传播。该恶意软件还开始使用LD-PRELOAD用户地rootkit来隐藏其进程。

SSH brute-force已经成为用Go编写的僵尸网络采用的主要攻击方式之一。我们发现了IPStorm的一个新的Linux变种,其中包括这种攻击向量。IPStorm是一个点对点(p2p)僵尸网络,于2019年5月首次被发现。它使用开源项目IPFS作为其网络骨干。除了原始的Windows变体,我们还发现了作为Linux变体的一部分,针对Android和物联网设备的变体。与本报告中的其他僵尸网络不同,IPStorm的目标不是安装矿机。相反,该僵尸网络似乎提供了一个代理网络。这个代理网络是作为互联网上的匿名代理网络出售的。

IPStorm不是唯一一个在2020年活跃的Go编写的p2p网络。2020年8月,Guardicore发布了一份关于他们从同年1月开始追踪的一个新的p2p僵尸网络的报告。该僵尸网络被命名为FritzFrog,通过强行使用弱小的凭证来感染机器。Guardicore称,该僵尸网络已经成功入侵了超过500台服务器,其中包括 “美国和欧洲的知名高教机构,以及一家铁路公司”。

5. 未来预测与结论

虽然与用其他语言编写的恶意软件相比,用Go编写的恶意软件数量相对较少,但同比增长幅度很大。这种增长速度很可能会继续下去,这意味着用Go编写的恶意软件将变得更加频繁。对于针对Linux环境的恶意软件来说,用Go编写的部分比针对Windows的恶意软件要大。这很可能导致,在根据针对特定系统的恶意软件总量统计中,针对Linux系统的恶意软件的比例将可能变得最大。

在目前用Go编写的Linux恶意软件中,有很大一部分是用于DDoS或安装密码器的机器人。这种趋势可能会持续下去。其他类型也可能会变得更加频繁。我们已经看到了针对Linux系统的Go勒索软件,而且有可能会出现更多的以窃取和加密有价值数据为目标的勒索软件。这与Proofpoint对2021年的预测一致,即勒索软件威胁行为者将开始更加关注攻击云端。这意味着企业应该采用专注于云的检测和预防产品,以确保他们的云环境受到保护。许多传统的防病毒和保护解决方案都是为了保护Windows环境而设计的,而Linux环境则更多地成为了”二等公民”。

根据CrowdStrike从2020年开始的事件报告,在40%的事件中,恶意软件没有被反病毒产品检测到。除此之外,Go恶意软件一直很难被反病毒产品检测到,所以这种趋势很可能会继续下去。我们已经看到威胁行为者以相同的恶意软件代码库为中心,针对不同的操作系统进行攻击,导致恶意软件样本较少或未被检测到。由于恶意软件来自相同的代码库,因此使用代码基因的检测方法非常有效。未来我们很可能会看到更多针对多个操作系统的恶意软件,因为像Go这样的编程语言为恶意软件作者提供了一种简单的交叉编译恶意软件的方法。

在Windows方面,许多威胁行为者已经使用Go来制作勒索软件。未来这种趋势很可能会继续下去。随着更多RaaS产品的出现,用Go编写勒索软件也不是不可能。由于能够轻松地进行交叉编译,RaaS运营商可以为他们的”客户”提供更广泛的目标。

Go是一种开源的编程语言,它是在Google内部开发的,目的是利用过去几十年在硬件上取得的进步。它的设计是为了让开发者能够轻松地制作快速、安全、以网络为中心的代码,并在当今的多核CPU上获益。这使得该语言得到了极大的应用,尤其是在云环境中。开发者并不是唯一采用Go的人。Go强大的跨平台交叉编译、优秀的网络实现和加密库以及原生的文件嵌入功能让其颇受恶意软件开发者的青睐! 在过去几年中,在市面上发现的用Go编写的新恶意软件几乎增加了2000%。这些恶意软件中有许多是针对Linux和物联网设备的僵尸网络,以安装加密矿机或将受感染的机器注册到DDoS僵尸网络中。此外,用Go编写的勒索软件似乎也变得更加普遍。一些用Go编写的著名勒索软件是Nefilim、EKANS和RobbinHood,这些勒索软件用于所谓的大型猎物攻击。

传统的反病毒解决方案似乎仍然难以检测到用Go编写的恶意软件。较新的技术不仅可以根据代码重用来判断恶意,还可以对威胁进行分类,已经取得了较大的成功,因为它们甚至可以处理Linux和Windows二进制文件之间的相似性。虽然用Go编写的恶意软件可能仍处于初级阶段,但它可能很快就会进入青春期,从而导致大量增加。


“Gopher部落”知识星球正式转正(从试运营星球变成了正式星球)!“gopher部落”旨在打造一个精品Go学习和进阶社群!高品质首发Go技术文章,“三天”首发阅读权,每年两期Go语言发展现状分析,每天提前1小时阅读到新鲜的Gopher日报,网课、技术专栏、图书内容前瞻,六小时内必答保证等满足你关于Go语言生态的所有需求!部落目前虽小,但持续力很强。在2021年上半年,部落将策划两个专题系列分享,并且是部落独享哦:

  • Go技术书籍的书摘和读书体会系列
  • Go与eBPF系列

Go技术专栏“改善Go语⾔编程质量的50个有效实践”正在慕课网火热热销中!本专栏主要满足广大gopher关于Go语言进阶的需求,围绕如何写出地道且高质量Go代码给出50条有效实践建议,上线后收到一致好评!欢迎大家订阅!目前该技术专栏正在新春促销!关注我的个人公众号“iamtonybai”,发送“go专栏活动”即可获取专栏专属优惠码,可在订阅专栏时抵扣20元哦(2021.2月末前有效)。

我的网课“Kubernetes实战:高可用集群搭建、配置、运维与应用”在慕课网热卖中,欢迎小伙伴们订阅学习!

img{512x368}

我爱发短信:企业级短信平台定制开发专家 https://tonybai.com/。smspush : 可部署在企业内部的定制化短信平台,三网覆盖,不惧大并发接入,可定制扩展; 短信内容你来定,不再受约束, 接口丰富,支持长短信,签名可选。2020年4月8日,中国三大电信运营商联合发布《5G消息白皮书》,51短信平台也会全新升级到“51商用消息平台”,全面支持5G RCS消息。

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻)归档仓库 – https://github.com/bigwhite/gopherdaily

我的联系方式:

  • 微博:https://weibo.com/bigwhite20xx
  • 微信公众号:iamtonybai
  • 博客:tonybai.com
  • github: https://github.com/bigwhite
  • “Gopher部落”知识星球:https://public.zsxq.com/groups/51284458844544

微信赞赏:
img{512x368}

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats