密码学与Base64编码
2024-03-22
概述
机密性问题
- 对称加密:
- 经典算法 DES,3DES,AES
- 分组模式
- 问题:中间人攻击:明文与密钥同时传送。中间人可取得密钥完成解密
- 密钥配送:解决中间人攻击问题。如:事先共享密钥等
- 非对称加密:RSA。存在问题如下
- 中间人冒充通讯对象,替换公钥
- 运算慢
- 对称+非对称混合:解决运算慢问题。对称加密明文,非对称加密密钥。实例:PGP、SSL/TLS。关键点如下
- 伪随机数生成器算法
- 对称密码分组模式
- 非对称密钥长度
认证问题
- 单项散列函数:用于防中间人篡改。别名:哈希、杂凑、摘要。输出消息,输出散列值
- 经典算法:MD5、SHA1、SHA-256、SHA3等
- 攻击:算法破解、暴力破解(撞库)、生日攻击(穷举,任意散列值一致概率)
- 问题:中间人伪装 - 无法判断消息是否来自正确的发送者
- 消息认证码(MAC):解决中间人伪装问题,防篡改,防伪装
- 实现:带密钥的hash函数。分组密码,单向散列,流密码等都可实现
- 原理:密钥和原明文组合,计算hash值(mac值)。中间人不知道密钥值,伪装消息时无法计算正确的mac值,无法伪装。做到了双方认证
- 应用:SWIFT、IPsec、SSL/TLS
- 攻击
- 重放攻击:中间人将截获的内容与mac保存,重复发送。解决:序号、时间戳、随机值nonce
- 密钥推测:对于非随机密钥。通过mac值逆向密钥值
- 问题
- 共享密钥不可泄漏(非对称加密或Diffie-Hllman)
- 第三方证明:需要防止否认。明确消息发送方,消息即可能为A发送,也可能为B发送。
- 数字签名:防篡改,防伪装、防否认。单向散列函数+非对称加密的反向使用
- 原理:没有私钥无法生成该私钥所生成的密文。持有密钥才能生成消息
- 过程
- A持有私钥,B持有公钥
- B向A发送消息,直接使用公钥加密后发送。私钥方通过私钥解密得到明文
- A发送消息时,A用私钥对明文hash值进行加密得到密文(即签名),将明文与签名都发送给B公钥方
- B用公钥对签名进行解密的到hash值,与明文的hash进行对比。确保数据源正常
- 应用:SSL/TLS
- 经典算法 :RSA、DSA、Rabin
- 攻击:
- 单向散列函数是否抗碰撞
- 数字签名攻击公钥密码(不对意思不清楚的消息进行签名)
- 问题
- 中间人攻击:无法确认公钥来自正确的通信对象。C想伪装成A。将B手中的公钥替换为C生成的公钥,C便可伪装成A进行通信(或者C持有A的私钥,也可伪装)
- 签名被盗用
- 公钥证书:第三方认证机构给公钥加数字签名,降低遭遇中间人攻击的风险(认证公钥来防止公钥被替换)。
- 原理:通过CA的公钥,得到A的公钥。再使用公钥对签名解密得到hash值,与正文hash值对比
- 公钥基础设施PKI:证书颁发管理
- 认证机构CA:证书管理人,对注册人的公钥加上数字签名
- 证书层级:可通过证书验证另一个证书公钥,实现递归
- 攻击
- 公钥注册证书前替换公钥
- 相似注册人名攻击
- 认证机构的私钥泄漏 发布CRL
- 伪装成认证机构
- CRL发布时差漏洞
编码流程中的单位问题
- 位(Bit):1个二进制
- 字节(Byte):8位二进制
- 字(word):在32位系统中,一个字4字节,共32位二进制;64位系统中,一个字8字节,共64位二进制
电子邮件传输算法 - Base64编码
为了解决早期电子邮件传输非ascii码字符时,经过有历史问题的网关(将非ascii码8位二进制最高位的数值置为0)时出现的问题。
经过Base64编码后的数据比原始数据略长,为原来的4/3倍。base64编码字符串的字符数是4的整数倍,结尾的等号用于补位
base64编码表
包含字符"A-Za-z0-9+/"
码值 | 字符 | 码值 | 字符 | 码值 | 字符 | 码值 | 字符 |
---|---|---|---|---|---|---|---|
0 | A | 16 | Q | 32 | g | 48 | w |
1 | B | 17 | R | 33 | h | 49 | x |
2 | C | 18 | S | 34 | i | 50 | y |
3 | D | 19 | T | 35 | j | 51 | z |
4 | E | 20 | U | 36 | k | 52 | 0 |
5 | F | 21 | V | 37 | l | 53 | 1 |
6 | G | 22 | W | 38 | m | 54 | 2 |
7 | H | 23 | X | 39 | n | 55 | 3 |
8 | I | 24 | Y | 40 | o | 56 | 4 |
9 | J | 25 | Z | 41 | p | 57 | 5 |
10 | K | 26 | a | 42 | q | 58 | 6 |
11 | L | 27 | b | 43 | r | 59 | 7 |
12 | M | 28 | c | 44 | s | 60 | 8 |
13 | N | 29 | d | 45 | t | 61 | 9 |
14 | O | 30 | e | 46 | u | 62 | + |
15 | P | 31 | f | 47 | v | 63 | / |
编码原理
- 流程
- 目标字符串以字符为单位转换成字符编码(ascii码)
- 将字符编码转换为二进制码
- 对二进制码进行分组转换,即每3个8位二进制码一组,转换为每4个8位二进制码为一组(不足6为低位补0)。注意方向从左往右
- 对每组中的6位二进制码高位补0,变为每组4个8位进制码
- 将变换后的二进制码换为10进制码
- 将10进制码对照码表得到结果字符串
- 例子:目标字符串
A
A
的ascii码为65
- ascii码的二进制码
01000001
- 从左往右每6位切分得到4-6二进制码
010000 010000
- 补0得到4-8二进制码
00010000 00010000
- 转换为10进制
16 16
- 对照码表,并补成4的倍数
QQ==
代码
Java API
import com.sun.org.apache.bcel.internal.generic.NEW; public class testbase64 { public static void main(String[] args) { System.out.println(Base64.encode("1101".getBytes())); System.out.println(new String(Base64.decode("MTEwMQ=="))) } }
C实现,参考https://blog.csdn.net/qq_26093511/article/details/78836087
/*base64.c*/ #include "base64.h" unsigned char *base64_encode(unsigned char *str) { long len; long str_len; unsigned char *res; int i,j; //定义base64编码表 unsigned char *base64_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; //计算经过base64编码后的字符串长度 str_len = strlen(str); if(str_len % 3 == 0) len = str_len/3*4; else len = (str_len/3+1)*4; res = malloc(sizeof(unsigned char)*len+1); res[len] = '\0'; //以3个8位字符为一组进行编码 for(i = 0, j = 0; i < len-2; j+=3,i+=4) { res[i] = base64_table[str[j]>>2]; //取出第一个字符的前6位并找出对应的结果字符 res[i+1] = base64_table[(str[j]&0x3)<<4 | (str[j+1]>>4)]; //将第一个字符的后位与第二个字符的前4位进行组合并找到对应的结果字符 res[i+2] = base64_table[(str[j+1]&0xf)<<2 | (str[j+2]>>6)]; //将第二个字符的后4位与第三个字符的前2位组合并找出对应的结果字符 res[i+3] = base64_table[str[j+2]&0x3f]; //取出第三个字符的后6位并找出结果字符 } switch(str_len % 3) { case 1: res[i-2] = '='; res[i-1] = '='; break; case 2: res[i-1] = '='; break; } return res; } unsigned char *base64_decode(unsigned char *code) { //根据base64表,以字符找到对应的十进制数据 int table[]={ 0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,62,0,0,0, 63,52,53,54,55,56,57,58, 59,60,61,0,0,0,0,0,0,0,0, 1,2,3,4,5,6,7,8,9,10,11,12, 13,14,15,16,17,18,19,20,21, 22,23,24,25,0,0,0,0,0,0,26, 27,28,29,30,31,32,33,34,35, 36,37,38,39,40,41,42,43,44, 45,46,47,48,49,50,51 }; long len; long str_len; unsigned char *res; int i,j; //计算解码后的字符串长度 len = strlen(code); //判断编码后的字符串后是否有= if(strstr(code,"==")) str_len = len/4*3-2; else if(strstr(code,"=")) str_len = len/4*3-1; else str_len = len/4*3; res = malloc(sizeof(unsigned char)*str_len+1); res[str_len] = '\0'; //以4个字符为一位进行解码 for(i=0,j=0;i < len-2;j+=3,i+=4) { res[j]=((unsigned char)table[code[i]])<<2 | (((unsigned char)table[code[i+1]])>>4); //取出第一个字符对应base64表的十进制数的前6位与第二个字符对应base64表的十进制数的后2位进行组合 res[j+1]=(((unsigned char)table[code[i+1]])<<4) | (((unsigned char)table[code[i+2]])>>2); //取出第二个字符对应base64表的十进制数的后4位与第三个字符对应bas464表的十进制数的后4位进行组合 res[j+2]=(((unsigned char)table[code[i+2]])<<6) | ((unsigned char)table[code[i+3]]); //取出第三个字符对应base64表的十进制数的后2位与第4个字符进行组合 } return res; }