scribble

Panic

About Blog Email GitHub

27 May 2015
块加密

概念

先从概念开始吧,加密算法分为对称加密算法和非对称加密算法。对称加密算法在加解密时使用相同的密钥;非对称加密算法, 也就是公钥加密算法,在加解密时使用的是不同的密钥。对称密码算法很容易理解,而非对称密码算法的应用,举一个简单例子: 我有两个密钥,公钥和私钥,公钥告诉大家,私钥我自己保存,这样每个人都把跟我通信的内容用公钥来加密,发给我, 我使用私钥来解密,这样就只有我一个人知道通信的内容了。当然不论对称加密还是非对称加密,加密算法本身并不是保密的, 只有密钥需要保密。

对称加密算法又分为:1、序列密码加密 2、分组加密。一次一密的加密方案是序列密码的雏形,一次一密是指在流密码中使用 与消息长度等长的随机密钥,密钥只使用一次;序列密码主要用于军事外交机密等。分组密码又称块加密(block cipher), 是以一定大小作为每次处理的基本单元,程序实现相对简单。

块加密

通常我们接触比较多的是AES加密算法,不过有一次跟媒体合作发现对方竟然还在使用DES。两种都是block cipher,在golang的 crypto/des基础库里还会看到TripleDESCipher,即3DES,即对同一个块使用不同的密钥加密三次,来提高安全性。AES不管是安 全性还是计算效率上都比DES都要高。

分组加密又分为四种分组密码模式:CBC,OFB,CFB,ECB。ECB电码本,每个块的加密都是独立的,因此可以并行进行;CBC是下 一个块在加密前与上一个块的加密结构亦或一下,这样就得逐个块加密了,安全性有所提高,适合长度长的报文,是SSL的标准, 发送方和接收方都要知道初始化向量;CFB类似CBC,稍微复杂一些;OFB更复杂一些就不再赘述了。了解这些内容以后,见到相关 的名词就不会那么陌生了,比如shadowsockets的账号信息里有加密方式一栏,默认是aes-256-cfb,意思就是使用AES加密算法, 块大小为256位,CFB模式。

golang的基础库对块加密的提供支持,crypto/aes和crypto/des分别提供aes和des的块加密算法,而块之间的组合计算在crypto/cipher 库中实现。crypto/cipher没有ECB模式,最初自己也跑去google有没有相关的实现,等了解块加密的基本概念后,发现自己真是傻, 块加密算法已经有了,ECB模式就只是逐个块进行加密而已。

那么既然是块加密,块的大小是固定的,例如块大小为128位,那我们的密钥也必须是128位,那么加密的明文如果不是块大小的 整数倍时就需要补全,保证密文的每一位都进行了加密。这个补全算法通常用PKCS7Padding,我们在加密前先对明文进行padding 补全,解密时先进行块解密在进行unpadding得到加密前的内容。

上代码吧,golang的AES的ECB模式加解密。

//@author: huhr
package security

import (
	"bytes"
	"crypto/aes"
)

func encrypt(plaintext []byte, key string) []byte {
	cipher, err := aes.NewCipher([]byte(key[:aes.BlockSize]))
	if err != nil {
		panic(err.Error())
	}
	// 加密之前需要进行padding
	if len(plaintext) % aes.BlockSize != 0 {
		panic("Need a multiple of the blocksize 16")
	}

	ciphertext := make([]byte, 0)
	text := make([]byte, 16)
	for len(plaintext) > 0 {
		// 每次运算一个block
		cipher.Encrypt(text, plaintext)
		plaintext = plaintext[aes.BlockSize:]
		ciphertext = append(ciphertext, text...)
	}
	return ciphertext
}

func decrypt(ciphertext []byte, key string) []byte {
	cipher, err := aes.NewCipher([]byte(key[:aes.BlockSize]))
	if err != nil {
		panic(err.Error())
	}

	if len(ciphertext) % aes.BlockSize != 0 {
		panic("Need a multiple of the blocksize 16")
	}

	plaintext := make([]byte, 0)
	text := make([]byte, 16)
	for len(ciphertext) > 0 {
		cipher.Decrypt(text, ciphertext)
		ciphertext = ciphertext[aes.BlockSize:]
		plaintext = append(plaintext, text...)
	}
	return plaintext
}

func PKCS7Pad(data []byte) []byte {
	padding := aes.BlockSize - len(data) % aes.BlockSize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(data, padtext...)
}

func PKCS7UPad(data []byte) []byte {
	padLength := int(data[len(data)-1])
	return data[:len(data)-padLength]
}

不同的语言对加解密实现的封装程度会不一样,在一些细节上也可能会有一些差别,跨语言时需要take care基础库的文档才行。


Til next time
at 19:08

scribble