密码学的应用-它是一个工具

对于使用者,它就是一个工具

提到密码学好像很复杂:加解密、签名验签、填充、分组、证书,总感觉概念很多,背后又涉及数论基础和复杂的数学定理,有没有一种想法,我就是一个使用者,不想成为科学家,更不想论证一遍这些算法,不用关心用到了什么定理什么数学底层知识或者不必那么深入研究,告诉我哪种是经过安全考验得到普遍认可的能解决我问题的就好,对于我来说它就是一个工具,在现实生活中我们大部分都是使用者角色

  • 家里的水管漏水了,于是到五金店老板推荐使用PVC管而且使用期限大概50年,我简单调研了下好像大家普遍用的就是这个材质,那就这个吧。为什么就能决定用它了:我不是第一个吃螃蟹的人,它经过了大量的使用如果有问题早就暴露出来了,至于年限50年这个房子在不在还两说,它要是能用20年我都能接受。
  • 我需要一台电脑用于编程,发现大家普遍用的都是MacBook Pro并且好像是程序员的标配,我去简单调研了下价格大概在2-3w,并未超出预算太多,于是决定就是它了,至于系统是否使用的习惯,普遍使用这个词代表它肯定不会太难用。对于我来说它就是一个工具而已

在密码学中也有很多这样的工具,而且普遍使用的都是免费的,这些工具的产生往往是科学家或者权威机构"制作"的,经过公众验证的,用于各个领域,作为使用者只需要知道这个工具是干什么的能否解决我的问题,如果这个工具足够有吸引力可能会去看看工具内部构造和制作"图纸"

加密工具

这个工具能解决什么问题 这是一个简单的系统交互图,系统之间传输数据,有些数据落地到数据库中,有些数据落地到本地,这些数据可能涉及到隐私数据,敏感数据,这些数据需要保护,无论是你的安全意识高还是基于监管的强制要求,你都需要将数据加密传输或存储,下面介绍几种常用的加密工具

对称加密 - AES

特点:对称、分组、速度、安全,加密任意数据大小

如果当前有大量的数据需要加密,例如:用户手机号,身份证号码,银行卡号等等,这些数据会在不同的系统之间传输或落地,总结一下:数据量大且不能太影响使用效率,这种场景推荐使用AES,这货由于通用、处理速到快、安全指数高,所以应用于各个场景也在各个编程语言中支持,下面给出go示例:

go 复制代码
// Encrypt AES/CBC/PKCS#7
func Encrypt(key, iv, data []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }
    mode := cipher.NewCBCEncrypter(block, iv)
    padData := pkcs7.Pad(data, aes.BlockSize)
    cipherText := make([]byte, len(padData))
    mode.CryptBlocks(cipherText, padData)
    return cipherText, nil
}

// Decrypt AES/CBC/PKCS#7
func Decrypt(key, iv, cipherText []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }
    mode := cipher.NewCBCDecrypter(block, iv)
    data := make([]byte, len(cipherText))
    mode.CryptBlocks(data, cipherText)
    originData, err := pkcs7.Unpad(data)
    if err != nil {
        return nil, err
    }
    return originData, nil
}

测试使用

go 复制代码
var (
    // 模拟一个手机号
    data = []byte("15100932122")
    // 长度可选:16|32|64 分别对应AES 128|256|512
    key = []byte("1234567890123456")
    // 注意长度需要是aes分组块大小
    iv = []byte("1234567890123456")
)

func TestEncrypt(t *testing.T) {
    cipherText, err := Encrypt(key, iv, data)
    if err != nil {
        t.Fatal(err)
    }
    cipherStr := base64.RawURLEncoding.EncodeToString(cipherText)
    t.Logf("data:%s,加密后:%s\n", string(data), cipherStr)
}

func TestDecrypt(t *testing.T) {
    cipherStr := "vVpKSF-8FmFszFmKVA_HxA"
    cipher, err := base64.RawURLEncoding.DecodeString(cipherStr)
    if err != nil {
        t.Fatal(err)
    }
    origin, err := Decrypt(key, iv, cipher)
    if err != nil {
        t.Fatal(err)
    }
    t.Logf("密文:%s,解密后:%s", cipherStr, string(origin))
}

完整代码:github.com/lidenger/cr... 说明下这个工具的3个关键关注点: 【KEY】、【分组工作模式】、【填充方式】

KEY

AES当前支持128/256/512密钥长度,长度越长安全性越高但处理速度就越慢

说明:

  • 25.7MB是读了一个go的sdk源码压缩文件,连续执行100次加解密
  • 10个手机号和银行卡号是每次随机产生的,每次对这10个测试数据进行加解密,共10万次,总计10万 * 10次加解密
  • 如果没有特殊原因直接使用256

分组工作模式

分组 AES可以加解密任意长度的数据关键在于分组,分组其实就是按固定长度对数据进行切分,每组16个字节 工作模式 分组工作模式简单理解为每组之间的联系模式,有的每组之间没有联系,有的作为了下一组的入参,有的引入了额外的参数,工作模式的引入会增加算法的安全性

  1. 下面这样每组独立没有任何联系的模式叫ECB
  1. 下面这样引入了额外的入参,每组的密文作为了下一组的入参叫CBC
  1. 下面这样每组独立但引入了额外数据和运算的叫CTR

填充方式

说完分组,填充就好理解了,AES要求每次加解密是16个字节也就是分组块的大小,但如果明文数据长度不是16的倍数那么必然会导致最后一组不满16个字节,这个时候就需要将最后一组填充到16个字节,例如下图这样

  1. NoPadding 数据正好为16个字节所以不需要填充
  2. PKCS7Padding 数据不足16个字节时使用缺失的字节数字补充,例如,最后一组数据为10个字节缺少6 个则后面都补充数字6

国密SM4

说到分组加密我国也有自己的算法,是由【国家密码安全局】发布的一系列类算法,和AES对标的是SM4,这个国内的金融机构相关应该很熟悉 《SM4分组密钥算法》:gmbz.org.cn/main/viewfi... 推荐一个不错的国密go依赖:github.com/tjfoc/gmsm

非对称加密 - RSA

特点:非对称,安全,处理有限数据

这个工具看上去好像没什么优点:处理速度又慢,处理的数据还有限,为什么会介绍这个工具,在介绍AES时说到对称这个词,对称就表示加解密使用相同的密钥,非对称则为加解密使用不同的密钥,加解密如果在两个分离的场景或团体执行就需要涉及到如何安全的传输(或协商)密钥的问题,而非对称一般由公钥和私钥两个KEY,使用时只需要将公钥传递给加密方即可,即使公钥被获取也没关系因为只有持有私钥才能解密,实际上在大多使用场景中都是使用非对称算法传递对称密钥(或者叫协商,如果对协商对称密钥感兴趣可以了解下DH算法)

go 复制代码
func GenerateKey(len int) *rsa.PrivateKey {
    privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
    if err != nil {
        panic(err)
    }
    return privateKey
}

// RSA-PKCS1加解密
func TestPKCS1(t *testing.T) {
    // 创建一个2048的key
    privateKey := GenerateKey(2048)
    testData := []byte("123abc")
    // 从私钥中拿到公钥进行加密
    cipher, err := rsa.EncryptPKCS1v15(rand.Reader, &privateKey.PublicKey, testData)
    if err != nil {
        t.Fatalf("RSA-PKCS1v15加密失败%s", err)
    }
    t.Logf("RSA-PKCS1v15密文为:%s", hex.EncodeToString(cipher))
    // 使用私钥解密
    origin, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, cipher)
    if err != nil {
        t.Fatalf("RSA-PKCS1v15解密失败%s", err)
    }
    t.Logf("RSA-PKCS1v15解密结果:%s", string(origin))
}

// RSA-OAEP加解密
func TestOAEP(t *testing.T) {
    privateKey := GenerateKey(2048)
    testData := []byte("123abc")
    // 设置一个label,可以是任意数据,加解密保持一致即可
    label := []byte("OAEP test encrypt and decrypt label")
    cipher, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, &privateKey.PublicKey, testData, label)
    if err != nil {
        t.Fatalf("RSA-OAEP加密失败%s", err)
    }
    t.Logf("RSA-OAEP密文为:%s", hex.EncodeToString(cipher))
    origin, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, privateKey, cipher, label)
    if err != nil {
        t.Fatalf("RSA-OAEP解密失败%s", err)
    }
    t.Logf("RSA-OAEP解密结果:%s", string(origin))
}

说下这个工具的2个关键关注点:【KEY】、【填充方式】

KEY

常用为2048/3072/4096这三种长度,实际上长度只要是2的倍就可以,当然最好不小于2048位,例如长度为3000、3080、12352这种也是可以的,长度越长安全性越高但处理速度就越慢 说明:每次对一个手机号进行加解密运算,共1000次

填充方式

和AES不同的是,RSA的填充更多是为了安全,每次填充后使源数据"扑朔迷离"增加攻击难度,上面提到RSA的处理数据是有限度的,那支持的明文到底是多长呢,这就要看是什么填充方式了,下面介绍两种: PKCS1 v1.5 明文最大长度为KEY的字节长度 - 11,例如:KEY的长度为2048位,那么明文最大长度为 256-11 = 245字节:

go 复制代码
k := pub.Size()
if len(msg) > k-11 {
    return nil, ErrMessageTooLong
}

第一位固定为0,第二为固定为2,明文前一位固定为0,从第三位到明文前第二位随机填充数据

OAEP 明文最大长度为KEY的字节长度-2倍hash长度-2,例如:KEY为2048位,hash为256位,那么明文最大长度为 256 - 2 * 32 - 2 = 190字节

go 复制代码
k := pub.Size()
if len(msg) > k-2*hash.Size()-2 {
    return nil, ErrMessageTooLong
}
  • 主要分为seed和db两个区域
  • seed区随机填充,这里用的是sha256所以随机填充32字节的数据
  • db区由label的摘要+0数据段+明文数据组成,示例中共223字节
  • seed+db交替做两次MFG运算,MFG = Counter + Hash + XOR
go 复制代码
func mgf1XOR(out []byte, hash hash.Hash, seed []byte) {
    var counter [4]byte
    var digest []byte

    done := 0
    for done < len(out) {
        hash.Write(seed)
        hash.Write(counter[0:4])
        digest = hash.Sum(digest[:0])
        hash.Reset()

        for i := 0; i < len(digest) && done < len(out); i++ {
            out[done] ^= digest[i]
            done++
        }
        incCounter(&counter)
    }
}

可以看出明文经过OAEP后完全失去了原貌,提高了攻击门槛,如果没有特殊原因可以直接选择OAEP

国密SM2

非对称国密也有对应的算法为SM2,虽然它属于ECC但支持非对称加解密 《SM2公钥加密算法》:gmbz.org.cn/main/viewfi...

编码

编码可以理解为数据的另一种展示形式,好比穿了不同的衣服一样,编码后的好处就是不存在特殊字符了,都在ASCII字符范围内,方便传输和存储

Hex

数据以16进制形式表示,即字符集为:0123456789abcdef,所有字符都映射到一组16进制字符数据中

go 复制代码
func TestHex(t *testing.T) {
	data := []byte("123abc中文")
	// 编码为16进制数据
	code := hex.EncodeToString(data)
	t.Logf("dataLen:%d,code:%s,codeLen:%d", len(data), code, len(code))
	// 解密为源数据
	origin, err := hex.DecodeString(code)
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("origin data:%s", string(origin))
}

说明:

  1. 字符串转为UTF-8字节
  2. 每个字节映射为固定的2个字节, 1 -> 2,例如: 49 -> 51 49
  3. 通过UTF-8转为16进制字符
  4. 转换为Hex编码后长度增加为源数据的2倍

映射逻辑

go 复制代码
hextable = "0123456789abcdef"

for _, v := range src {
    dst[j] = hextable[v>>4]
    dst[j+1] = hextable[v&0x0f]
    j += 2
}

Base64

和Hex类似,Base64将数据固定映射为64个字符组成的数据集合 标准的64个字符为: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ 由于+和/在URL中是有特殊含义的所以这两个字符被替换为-和_,替换后的64个字符集合称为Base64 url safe, 即:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_

go 复制代码
// 无padding的标准base64
func TestRawStdEncoding(t *testing.T) {
	data := []byte("123abc中文测试数据123ccddEncodeToString xxc")
	encode := base64.RawStdEncoding.EncodeToString(data)
	t.Logf("encode:%s", encode)
	origin, err := base64.RawStdEncoding.DecodeString(encode)
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("decode:%s", string(origin))
}

// 无padding的url safe base64
base64.RawURLEncoding.EncodeToString(data)
base64.RawURLEncoding.DecodeString(encode)

// 有padding的标准base64
base64.StdEncoding.EncodeToString(data)
base64.StdEncoding.DecodeString(encode)

// 有padding的url safe base64
base64.URLEncoding.EncodeToString(data)
base64.URLEncoding.DecodeString(encode)

映射逻辑和Hex类似也是固定映射到指定字符集中:

  1. 源数据3个字节通过映射逻辑映射到4个字节,3 -> 4
  2. 每3个字节为一组,那么余数只可能为:0,1,2这三个数,0就是刚好分完那就不存在padding的情况,如果是1或2就需要padding了,固定padding字符: =,余数为1补充2个=字符,余数为2补充1个=字符
  3. 经过base64编码后长度是源数据的4/3倍(1.333)

映射逻辑:

go 复制代码
const encodeStd = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
const encodeURL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"

di, si := 0, 0
n := (len(src) / 3) * 3
for si < n {
    // Convert 3x 8bit source bytes into 4 bytes
    val := uint(src[si+0])<<16 | uint(src[si+1])<<8 | uint(src[si+2])

    dst[di+0] = enc.encode[val>>18&0x3F]
    dst[di+1] = enc.encode[val>>12&0x3F]
    dst[di+2] = enc.encode[val>>6&0x3F]
    dst[di+3] = enc.encode[val&0x3F]

    si += 3
    di += 4
}

remain := len(src) - si

摘要

摘要:摘取要点,概要,内容提要 例如,我们小学学习的缩句

  • 可爱的小红在认真地读着有趣的故事书 -> 小红读书
  • 渔夫的妻子桑娜坐在火炉旁补一张破帆 -> 桑娜补帆

那么是怎么做的呢:

去掉句子中多余的修饰成分,留下主谓宾等核心信息

使用场景

密码学中的摘要是怎么使用的呢 场景1 :研发时需要下载一个最新的GO SDK,例如当前的系统为m芯片的macOS,于是到了官网下载页面, 如图所示,选择了darwin-arm64的文件,后面使用橙色框中的部分其实就是这个文件的摘要,标题为SHA256可以推断出摘要算法为SHA256,根据摘要的字符组合可以推断出编码形式为Hex,下面验证下:

go 复制代码
//go:embed go1.21.6.darwin-arm64.pkg
var TestGo121File []byte

func GetTestGo121File() []byte {
	return TestGo121File
}


func TestSha256(t *testing.T) {
	data := testdata.GetTestGo112File()
	digested := sha256.Sum256(data)
	hexStr := hex.EncodeToString(digested[:])
	t.Logf("sha256 hex:%s", hexStr)
}

输出结果:

go 复制代码
=== RUN   TestSha256
    digesteduse_test.go:14: sha256 hex:8f8b5290c9157a5ea65c994dd476d3262f2f828c4a00bd1b135f1bd81bda0039
--- PASS: TestSha256 (0.16s)
PASS

可以看到摘要信息是一致的,那么这又能说明什么呢?先看下整体的下载数据传输示意图:

数据经过了不安全的公共网络信道传输到本地,当前我们不确定下载的文件有没有被篡改还是不是官网打包放到页面上的源文件,通过在本地做文件的摘要来和官网提供的摘要做对比,对比一致就说明还是源文件,我们就可以放心使用了

场景2 :涉及到账号密码登陆的系统时出于安全要求不能存储明文密码,用户输入的是明文密码那么我们是如何验证账号密码是否正确的呢

  1. 注册或重置密码时将密码摘要信息存储到数据库中
  2. 用户登陆时通过账号获取到数据库中密码摘要
  3. 使用用户输入的密码和数据库中获取的摘要做对比

摘要算法

摘要主要关注的2个点

  • 不同源数据产生摘要的重复率,重复率越低越好
  • 通过摘要反推源数据的难度,难度越高越安全

根据摘要关注的核心点也出现了不同的摘要算法

MD

Message Digested MD家族:MD2、MD4、MD5

go 复制代码
func TestMd5(t *testing.T) {
	data := []byte("123abc")
	digested := md5.Sum(data)
	t.Logf("digested:%s", hex.EncodeToString(digested[:]))
}

摘要长度为128位,MD系列的安全性较低不建议使用

SHA

Secure Hash Algorithm SHA是当前常用的也是比较安全的摘要算法,摘要长度越长安全性越高,SHA家族:

  • SHA1 => 160位 【低安全】
  • SHA224 => 224位
  • SHA256 => 256位
  • SHA384 => 384位
  • SHA512 => 512位
go 复制代码
func TestSha(t *testing.T) {
	data := []byte("123abc")
	sha256.Sum224(data)
	sha256.Sum256(data)
	sha512.Sum384(data)
	sha512.Sum512(data)
}

MAC

Message Authentication Code 和上面两种摘要不同的是:MAC是带有密钥的并可以和MD,SHA组合使用,例如mac + sha256:

go 复制代码
func TestMac(t *testing.T) {
	key := []byte("mySecretKey")
	data := []byte("123abc")
	h := hmac.New(sha256.New, key)
	h.Write(data)
	digested := h.Sum(nil)
	t.Logf("digested:%s", hex.EncodeToString(digested))
}

说明:

  1. 将key按照不同的值异或后得到ipad和opad
  2. 使用传入的摘要算法(例如:sha256)分为内部和外部两个
  3. 使用内部摘要算法将ipad+源数据执行摘要运算得到中间摘要
  4. 使用外部摘要算法将opad+中间摘要结果执行摘要运算得到最终摘要结果
go 复制代码
// 将传入的摘要算法分为内部和外部
hm := new(hmac)
hm.outer = h()
hm.inner = h()

// 根据key和常量异或得到内部padding和外部的padding
copy(hm.ipad, key)
copy(hm.opad, key)
for i := range hm.ipad {
    hm.ipad[i] ^= 0x36
}
for i := range hm.opad {
    hm.opad[i] ^= 0x5c
}

// ipad + 源数据获取摘要
hm.inner.Write(hm.ipad)
in = h.inner.Sum(in)
// opad + 中间摘要获取最终摘要结果
h.outer.Write(h.opad)
h.outer.Write(in[origLen:])
h.outer.Sum(in[:origLen])

签名

签名,签上自己的名字?是的,生活中我们在签署租房合同,劳动合同,办理银行卡都要签上自己的名字,签名的含义一是代表"我的",二是我已知晓并认可上面的内容,一般都具有一定的法律效力,既然有涉及到法律那就不是闹着玩的,万一有人伪造了我们的签名搞事情那就不好了 在密码学中签名也是类似的作用:确认信息来源身份 下面来看一个场景: 数据由A经过公共网络传输到B,B如何确定数据data是A的,并且传输途中没有经过篡改,我们通过以下方式尝试确定A:

  • A的IP,好像不太行:1. IP只能确定某个机器,不能确定是哪个应用;2. 在高可用的要求下A应用都是以集群形式存在会涉及到动态扩缩容,B要跟随A的集群变更吗?显然不合理
  • A在传输data时增加data的摘要信息,就像我们在上面将摘要时说的那样,好像也不行,摘要信息随数据传输如果数据给篡改那么数据对应的摘要信息也会被篡改

其实很好解决,只要在传输data时增加A对data的签名就可以了

  1. A生成公私钥,B拿到A的公钥(B如何安全得到公钥是另外一个话题,这里不详细说明)
  2. A使用私钥对data签名得到签名数据
  3. A将data和签名一起传输
  4. B拿到数据和签名
  5. B使用A的公钥 + 数据 + A的签名进行验签,验签通过证明是A的数据
go 复制代码
// RSA
func TestRsaSign(t *testing.T) {
	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		t.Fatal(err)
	}
	// 源数据
	data := []byte("123abc")
	digested := sha256.Sum256(data)

	// 使用私钥 sha256 + PKCS1v15 算法签名
	sign, err := rsa.SignPKCS1v15(nil, privateKey, crypto.SHA256, digested[:])
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("sign:%s", hex.EncodeToString(sign))

	// 使用公钥验签
	err = rsa.VerifyPKCS1v15(&privateKey.PublicKey, crypto.SHA256, digested[:], sign)
	if err != nil {
		t.Logf("验签失败:%+v", err)
	} else {
		t.Log("验签通过")
	}
}

// ECC
func TestEcdsaSign(t *testing.T) {
	privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	data := []byte("123abc")
	digested := sha256.Sum256(data)
	// esdsa签名
	r, s, err := ecdsa.Sign(rand.Reader, privateKey, digested[:])
	sign := r.Bytes()
	sign = append(sign, s.Bytes()...)
	t.Logf("sign:%s", hex.EncodeToString(sign))
	// 验签
	isVerify := ecdsa.Verify(&privateKey.PublicKey, digested[:], r, s)
	if isVerify {
		t.Log("验签通过")
	} else {
		t.Log("验签失败")
	}
}

代码中有两类签名算法

  • RSA :老牌的签名算法,被广泛支持,这就意味着兼容性好
  • ECC :基于椭圆曲线算法,在安全性上256位的ECC相当于3072位的RSA,ECC可以在保持很小的签名结果情况下不降低安全性,这也就是为什么在增加github的ssh key时推荐使用ECC类型的原因
shell 复制代码
# 推荐的
ssh-keygen -t ed25519 -C "[email protected]"

# 替代方案
# Note: If you are using a legacy system that doesn't support the Ed25519 algorithm, use
ssh-keygen -t rsa -b 4096 -C "[email protected]"

证书

身份证

证书和身份证类似,身份证可以证明:

  1. 我是谁
  2. 合法公民

总结身份证上的信息 国徽面:

  • 国徽图案+签发机关:合法颁发机构
  • 有效期:起始和截止时间,有时候不做验证,有的需要,例如银行会提醒你证件过期可能会影响某些业务

人像面:

  • 姓名:证件的所有者名称,通用名
  • 性别,名族,出生年月,头像,住址:证件所有者的基础信息,属性
  • 身份证号:所有者的唯一标识,从某种意义上来说整个证件就是为了证明这个号码的合法性(号码中某一位也用到了摘要,你们知道是哪位吗)

Web证书

在Web服务中,只要是HTTPS协议通常都需要配置证书,证书可以证明我们浏览的网站是合法的,在浏览github时地址左边有证书查看入口,如图: 是不是和身份证很类似:

yaml 复制代码
所有者名称:	github.com
颁发机构:	DigiCert Inc
基础信息:版本、序列号、扩展密钥用法
截止日期	2024年3月15日星期五 07:59:59
证书摘要	92a37fbd5e21a53a95c716e1144f442f582b94d0fafc673eb6717a4eb51a88a7
公钥摘要	607f3e97a3c3bc8a35439a3abdaaefc3679d3e07f22456397c7b9296c55dbdd7

下面我们来看看整体交互流程: 说明步骤,我们也类比生活中做高铁的流程

  1. github向证书颁发结构(CA)申请自己的证书(向公安部门申请身份证)
  2. 当我们在浏览器访问github.com时,浏览器获取到相应的证书(拿到了身份证)
  3. 浏览器在系统信任的根证书中验证github的证书合法性(高铁闸机或人工审核身份是否合法以及购票信息)
  4. 证书合法后正常浏览github.com(验证通过,乘坐高铁)

系统中的根证书列表:

这里涉及到信任的传递:

  1. 浏览器和github都信任CA,证书又是CA颁发的,验证通过后浏览器自然信任github的证书
  2. 我们和服务方都信任国家,由国家颁发的身份证验证通过后自然能证明自己

上面说到身份证主要信息是身份证号,那么web证书的核心信息是证书中的公钥信息,可以简单理解为证书在某种意义上就是为了证明公钥的合法性 可以通过openssl工具查看证书信息

bash 复制代码
openssl x509 -noout -text -in github.com.cert

也可通过程序

go 复制代码
//go:embed github.com.cert
var TestCertFile []byte


func TestCert(t *testing.T) {
    certBytes := testdata.GetTestCertFile()
    block, _ := pem.Decode(certBytes)
    if block == nil {
        t.Fatal("解析证书数据失败")
    }
    // 获取证书内容
    certificate, err := x509.ParseCertificate(block.Bytes)
    if err != nil {
        t.Fatal(err)
    }
    // 获取证书中的公钥
    publicKey := certificate.PublicKey
    t.Logf("public:%+v", publicKey)
}

证书中许多属性,例如:说明证书的作用;使用场景;指定只能给某些域名使用,只要验证双方约定好规则就可以正常工作 如果我们申请一个公共的证书一般都是要收费的,但如果我们只是内部使用就可以生成自签的私有证书:

  1. 生成一个自签的根证书
bash 复制代码
# 生成根证书私钥
openssl genrsa -out root.key 4096
# 生成根证书 (/C=国家/ST=街道/L=省市/O=集团/OU=部门/CN=通用名)
openssl req -new -x509 -key root.key -subj "/C=cn/ST=beijing/L=beijing/O=lidenger/OU=software/CN=lidengerroot" -addext "extendedKeyUsage=clientAuth,serverAuth" -days 3650 -out root.crt 
  1. 使用自签根证书私钥签发服务证书
bash 复制代码
# 生成服务证书私钥
openssl genrsa -out server1.key 4096
# 使用根证书私钥签发服务证书
openssl req -new -x509 -key server1.key -subj "/C=cn/ST=beijing/L=beijing/O=lidenger/OU=software/CN=lidengerserver1" -addext "extendedKeyUsage=clientAuth,serverAuth" -days 3650 -out server1.crt -CA root.crt -CAkey root.key

解释下生成证书命令参数:

  • -subj 指定使用者名称
  • -addext 扩展属性,示例中的"extendedKeyUsage=clientAuth,serverAuth"代表证书可以用做服务端认证和客户端认证
  • -days 指定证书有效天数
  • CA / CAkey 指定根证书和根私钥

当我们直接使用时发现根证书和服务证书都是验证失败的 这时候只需要将根证书安装到本机证书库中即可 所以理论上只要系统中存在我们的根证书,那么通过根证书签发的所有服务证书都是合法的

总结

介绍了密码学中的:

  • 对称加密:加解密使用相同的密钥,安全性高,处理速度快,处理数据量大
  • 非对称加密:加解密使用了公钥和私钥两个密钥,安全性高,私钥不需要传输
  • 编码:编码后的数据更方便传输
  • 摘要:证明原始数据未被修改
  • 签名: 证明数据来源身份
  • 证书:综合了密码学中的各类工具的使用

本文意在说明这些工具是什么,使用场景,如何使用,以及简要说明了内部细节,其实还有很多点没有涉及到,如果感兴趣可以继续深入学习和研究~

摸鱼时刻

给大家推荐一款高端中型热带鱼: 埃及神仙鱼

原产地为南美洲的委内瑞拉境内的欧利那可河和哥伦比亚的Rio Orinoco流域及其上游的支流Rio Atabapo均有发现。外形与神仙鱼相似,但体形较大,长可达15厘米,体色银灰中带浅黄,背部黄色较深,腹部较浅,且在背部有小的棕色斑点。体侧也有4条垂直黑条纹。黑眼珠红眼圈。额头的角度较一般普通的神仙鱼高,因此显得嘴巴看起来略为似乎往上翘起。它们的背鳍和臀鳍的高度相较一般普通的神仙鱼而言,也显得额外高而修长。埃及神仙鱼的最适生长水温24-28℃,喜弱酸性软水,溶氧量丰富,水域宽阔,有阔叶水草和较好的光照条件,饵料鲜活,也吞食鱼苗。大神仙鱼爱静,易受惊,受惊以后,对侵犯干扰能迅速作出愤怒抗争的反应。有时爱欺侮不同种类的神仙鱼、小鱼等,故不宜混养。杂食性,适合在有水草和沉木的水族箱中,适合单养或与同类鱼混养

相关推荐
Alfadi联盟 萧瑶5 小时前
未来安全与持续进化
安全
SmartSoftHelp开发辅助优化9 小时前
SmartSoftHelp 之 SQL Server 数据库安全备份与安全还原详解---深度优化版:SmartSoftHelp DeepCore XSuite
数据库·安全·oracle
Alfadi联盟 萧瑶10 小时前
安全运营与威胁对抗体系
安全
PrDarcyLuo10 小时前
【IEEE会议推荐】第五届区块链技术与信息安全国际会议
人工智能·安全·网络安全·区块链·信息与通信
zhu620197610 小时前
Android10如何设置ro.debuggable=1?
android·安全·android逆向·android10·ro.debuggable
广州创科水利12 小时前
案例分享——福建洋柄水库大桥智慧桥梁安全监测
物联网·安全·数据分析·自动化
上海云盾商务经理杨杨12 小时前
2025年高防IP与游戏盾深度对比:如何选择最佳防护方案?
网络协议·tcp/ip·安全·web安全·游戏
Think Spatial 空间思维12 小时前
【安全攻防与漏洞】HTTPS中的常见攻击与防御
网络协议·安全·https·漏洞·攻防·防御
0xCC说逆向13 小时前
Windows逆向工程提升之IMAGE_EXPORT_DIRECTORY
开发语言·数据结构·windows·安全·网络安全·pe结构·逆向工程
网硕互联的小客服13 小时前
如何排查服务器 CPU 温度过高的问题并解决?
linux·运维·服务器·网络·安全