密码学与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+/"

码值字符码值字符码值字符码值字符
0A16Q32g48w
1B17R33h49x
2C18S34i50y
3D19T35j51z
4E20U36k520
5F21V37l531
6G22W38m542
7H23X39n553
8I24Y40o564
9J25Z41p575
10K26a42q586
11L27b43r597
12M28c44s608
13N29d45t619
14O30e46u62+
15P31f47v63/

编码原理

  • 流程
    • 目标字符串以字符为单位转换成字符编码(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/78836087open in new window

    /*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;
    }