尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
分组密码填充(Padding)完全指南:PKCS7、0x80 填充规则与不校验填充引发的 CTF 攻击
文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载本文以 CTF Wiki 中 填充方式 一节为骨架系统梳理分组密码中常见的五种填充规则及其字节级示例并深入剖析 2018 上海市大学生网络安全大赛aessss这道真题题目在pad/unpad实现中既不校验填充合法性、又以 AES S 盒作为字节置换表最终被攻击者通过操控填充长度逐字节还原出完整 flag。读完本文你将掌握 PKCS#5/#7、0x80、ANSI X9.23 风格、全零与空格填充的格式差异理解填充校验缺失为什么是最危险的密码学实现缺陷之一并拿到一份可直接复现的完整利用脚本。为什么分组密码需要填充分组密码Block Cipher一次只能加密一个固定长度的数据块例如 DES 的块大小为 8 字节AES 的块大小为 16 字节。然而实际明文消息的长度几乎不会恰好是块大小的整数倍因此在加密之前必须对最后一块进行填充Padding使其补齐到完整块长。关于填充有几个必须牢记的事实即使消息长度恰好是块大小的整数倍仍然需要填充。这是为了保证解密的唯一性接收方依赖填充规则来判断哪些字节属于填充、应当被剥离。如果长度恰为整数倍却不填充接收方将无法区分最后一整块全是数据和最后一块恰好包含填充容易产生歧义甚至被攻击者利用典型如 Padding Oracle Attack。填充的正确性检验是安全边界。规范的做法是解密之后必须校验填充是否合法一旦发现 Padding 不正确就抛出异常而恰恰是这个是否会报错的差异构成了攻击者可利用的 Oracle预言机——攻击者可以通过观察服务器是否报错来逐字节推断明文。与之相反完全不校验填充同样危险。如果unpad仅凭最后一个字节的值直接截断消息攻击者就可以通过篡改最后一个字节任意控制被剥离的字节数。这正是下文 aessss 题目的核心漏洞。五种常见填充规则与字节级示例原文档给出了一份常见填充规则的速览全部以 DES8 字节块为例明文为for16 进制66 6F 72占 3 字节需要填充 5 个字节密钥固定为01 23 45 67 89 AB CD EF。下表先给出总览随后逐一展开填充方式填充后的输入块16 进制DES 输出块核心特征PKCS#5 / PKCS#766 6F 72 05 05 05 05 05FD 29 85 C9 E8 DF 41 40填充 n 个字节每字节值均为 nOneAndZeroes0x80 零66 6F 72 80 00 00 00 00BE 62 5D 9F F3 C6 C8 40先补0x80再补零ANSI X9.23 风格66 6F 72 00 00 00 00 0591 19 2C 64 B5 5C 5D B8补零最后一个字节为填充长度Zero Padding全零66 6F 72 00 00 00 00 009E 14 FB 96 C5 FE EB 75全部补0x00有歧义空格填充66 6F 72 20 20 20 20 20E3 FF EC E5 21 1F 35 25全部补0x20非标准PKCS#5 / PKCS#7以填充字节数本身作为填充值规则假设还缺n个字节1 n 块大小则追加n个值为n的字节。若消息长度恰好是块大小的整数倍则追加一整块值为块大小的填充如 AES-128 追加 16 个0x10从而保证解填充时永远能通过最后字节值 块大小这一校验且不会与真实数据混淆。DES INPUT BLOCK f o r _ _ _ _ _ (IN HEX) 66 6F 72 05 05 05 05 05 KEY 01 23 45 67 89 AB CD EF DES OUTPUT BLOCK FD 29 85 C9 E8 DF 41 40这里需要区分两个名字PKCS#5最初定义于 8 字节块如 DES而PKCS#7将其推广到任意块大小最长 255 字节。由于 AES16 字节块时代多数库仍沿用 PKCS5Padding 的叫法实践中两者经常混用加密领域通常直接称这类填充为 PKCS 填充。在 Padding Oracle Attack 一节中攻击面正是建立在采用 PKCS5 Padding 且能观察填充校验结果之上的因此它是所有填充方式中攻防讨论最密集的一种。OneAndZeroes Padding0x80 后接零字节规则先填充一个0x80即二进制1000 0000再填充足够数量的0x00直到块边界。这个方案天然是自定界的——接收方只要找到第一个0x80即可确定数据结束位置因此即使消息长度恰为块大小的整数倍也可以选择不填充。DES INPUT BLOCK f o r _ _ _ _ _ (IN HEX) 66 6F 72 80 00 00 00 00 KEY 01 23 45 67 89 AB CD EF DES OUTPUT BLOCK BE 62 5D 9F F3 C6 C8 40正如原文档指出的先补0x80再补零的结构与MD5、SHA-1 等哈希函数的填充方式几乎一致哈希函数在消息末尾追加一个0x80位、随后补零直至特定长度边界再追加消息长度字段MD5 还会再补一个 64 位小端长度。区别仅在于哈希填充后续还附加了长度信息而分组密码的 0x80 填充到块边界即止。ANSI X9.23 风格零字节 末字节为填充长度规则用0x00填充剩余空间但最后一个字节必须是填充字节数。这样解填充时读取末字节即可知道应剥离多少字节缺点是若校验不严例如不验证前面的零字节同样存在被操控的风险。DES INPUT BLOCK f o r _ _ _ _ _ (IN HEX) 66 6F 72 00 00 00 00 05 KEY 01 23 45 67 89 AB CD EF DES OUTPUT BLOCK 91 19 2C 64 B5 5C 5D B8Zero Padding全零填充规则直接用0x00填满最后一个块不携带任何长度信息。DES INPUT BLOCK f o r _ _ _ _ _ (IN HEX) 66 6F 72 00 00 00 00 00 KEY 01 23 45 67 89 AB CD EF DES OUTPUT BLOCK 9E 14 FB 96 C5 FE EB 75这种方式的致命弱点是歧义若明文本身以0x00结尾接收方无法确定哪些零是填充、哪些是数据必须额外依赖消息长度字段。因此它只适用于消息长度恒定的场景如固定长度随机数加密。空格填充规则用0x20ASCII 空格填满最后一块。这是一种非标准的临时方案同样具有歧义仅在明文确定不含空格字符的特定场景下才可逆。DES INPUT BLOCK f o r _ _ _ _ _ (IN HEX) 66 6F 72 20 20 20 20 20 KEY 01 23 45 67 89 AB CD EF DES OUTPUT BLOCK E3 FF EC E5 21 1F 35 25填充校验Oracle 与失控的两个极端把上述五种方案放到攻防视角下看会发现两条截然相反却同样致命的攻击路径过度暴露校验结果 → Padding Oracle。若服务器在解填充失败时抛出异常或返回可区分的错误攻击者就能通过枚举密文最后一个字节、观察是否报错逐步还原出D_K(C) ⊕ IV的每一字节从而在不知道密钥和 IV 的前提下解密任意密文。这正是 Padding Oracle Attack 一节的完整攻击框架。完全不校验 → 填充长度可被任意操控。若unpad只是读到最后一个字节、去掉那么多字节那么攻击者把最后一个字节改成任意值v就能让解密后的消息被截断v个字节——相当于获得了对消息长度的写权限。下面的 aessss 题目就是这条路径的教科书案例。实战案例2018 上海市大学生网络安全大赛 aessss该题把不校验的 unpad与AES S 盒置换结合构造了一个可逐字节爆破出 flag 的加密服务。题目脚本服务端Python 2基于SocketServer与Crypto.Cipher.AES源码如下import random import sys import string from hashlib import sha256 import SocketServer from Crypto.Cipher import AES from secret import FLAG, IV, KEY class Task(SocketServer.BaseRequestHandler): def proof_of_work(self): proof .join( [random.choice(string.ascii_lettersstring.digits) for _ in xrange(20)]) # print proof digest sha256(proof).hexdigest() self.request.send(sha256(XXXX%s) %s\n % (proof[4:], digest)) self.request.send(Give me XXXX:) x self.request.recv(10) x x.strip() if len(x) ! 4 or sha256(xproof[4:]).hexdigest() ! digest: return False return True def pad(self, s): s (256 - len(s)) * chr(256 - len(s)) ret [\x00 for _ in range(256)] for index, pos in enumerate(self.s_box): ret[pos] s[index] return .join(ret) def unpad(self, s): ret [\x00 for _ in range(256)] for index, pos in enumerate(self.invs_box): ret[pos] s[index] return .join(ret[0:-ord(ret[-1])]) s_box [ 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 ] invs_box [ 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D ] def encrypt(self, msg): cipher AES.new(KEY, AES.MODE_CBC, IV) return cipher.encrypt(msg).encode(hex) def handle(self): if not self.proof_of_work(): return self.request.settimeout(15) req self.request flag_len len(FLAG) assert(flag_len 33) self.flag self.pad(FLAG) assert(len(self.flag) 256) while True: req.sendall( Welcome to AES(WXH) encrypt system.\n1. get encrypted flag.\n2. pad flag.\n3.Do some encrypt.\nYour choice:) cmd req.recv(2).strip() try: cmd int(cmd) except ValueError: cmd 0 if cmd 1: enc self.encrypt(self.flag) req.sendall(Here is the encrypted flag: 0x%s\n % enc) elif cmd 2: req.sendall(Pad me something:) self.flag self.unpad(self.flag)[ :flag_len] req.recv(1024).strip() assert(len(self.flag) 256) self.flag self.pad(self.flag) req.sendall(Done.\n) elif cmd 3: req.sendall(What do you want to encrypt:) msg self.pad(req.recv(1024).strip()) assert(len(msg) 256) enc self.encrypt(msg) req.sendall(Here is the encrypted message: 0x%s\n % enc) else: req.sendall(Do not lose heart % Once WXH AK IOI 2019 can Solved! WXH is the first in the tianxia!) req.close() return class ThreadedServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): pass if __name__ __main__: HOST, PORT 0.0.0.0, 23333 print Run in port:23333 server ThreadedServer((HOST, PORT), Task) server.allow_reuse_address True server.serve_forever()漏洞分析题目暴露给攻击者的三个操作是choice 1返回encrypt(self.flag)即当前 flag已填充为 256 字节的 AES-CBC 密文。由于消息恰好是 256 字节一个 AES 分组CBC 退化为C E(P ⊕ IV)因此两次明文相同的加密结果必然相同这是爆破比对的基础。choice 2self.flag self.unpad(self.flag)[:flag_len] 用户输入即先对当前 flag 逆 S 盒置换并 unpad保留前 33 字节再追加用户输入并重新 pad。攻击者可以通过这个入口改写flag 内容。choice 3对任意用户输入做 pad 后加密返回可用于构造参照密文本题实际未使用。漏洞出在pad/unpad这一对函数上。先看paddef pad(self, s): s (256 - len(s)) * chr(256 - len(s)) ret [\x00 for _ in range(256)] for index, pos in enumerate(self.s_box): ret[pos] s[index] return .join(ret)两点观察填充值即缺多少补多少s (256 - len(s)) * chr(256 - len(s))追加的每个字节都等于所缺字节数。如果len(s) 256追加 0 个字节——即明文足够 256 字节时完全不填充。s_box是标准的 AES S 盒对照 AES 字节替换 一节可知s_box首行63 7C 77 7B F2 6B 6F C5 30 01 67 2B FE D7 AB 76正是 AES S 盒第 0 行invs_box首行52 09 6A D5 30 36 A5 38 BF 40 A3 9E 81 F3 D7 FB正是 AES 逆 S 盒。由于 S 盒是 0~255 的完整置换这里被当作一张固定的字节位置置换表使用ret[pos] s[index]表示把第index个字节挪到第s_box[index]个位置。unpad则用逆 S 盒把顺序还原回来。再看unpaddef unpad(self, s): ret [\x00 for _ in range(256)] for index, pos in enumerate(self.invs_box): ret[pos] s[index] return .join(ret[0:-ord(ret[-1])])最大的问题在于unpad 没有任何校验它既不去验证被剥离的字节是否都等于最后一个字节的值也不检查该值是否落在合法区间PKCS 要求 1~块大小而是直接根据ret[-1]还原顺序后的最后一个字节的数值把后面那么多字节全部当作 padding 去掉。于是攻击者只需控制追加数据的最后一个字节就能任意指定被截断的字节数。这正对应 填充方式 开头强调的填充正确性问题规范的实现应当校验并抛异常这里却把它交给了攻击者掌控。攻击思路flag 长度为 33题目断言flag_len 33。攻击流程如下让 flag 免填充选择 choice 2追加256 - 33 223个字节使当前self.flag恰好为 256 字节、无需再填充同时把追加内容的最后一个字节设为chr(256 - 32) chr(224)。服务端处理服务器对33 字节 flag 223 字节输入进行 pad无操作与 S 盒置换结果赋给类中的self.flag变量。触发可控截断再次选择 choice 2服务器会对当前self.flag做逆 S 盒置换并 unpad。由于unpad只看最后一个字节值为 224它会把后面 224 个字节全部当成 padding 去掉明文只剩真正 flag 的前 32 位flag[:32]。追加猜测字节此时输入一个字符c服务器把flag[:32] c作为新的 33 字节明文再 pad 成 256 字节。如果c恰好等于 flag 的最后一个字节那么flag[:32] c就是完整 flag其填充结果与原始 flag 的填充结果逐字节完全相同。密文比对选择 choice 1 加密当前内容与最初拿到的flag_enc原始 flag 的密文比对。相同即爆破成功得到 flag 最后一个字节。逐字节爆破对flag从最后一个字节往前逐字节重复上述过程第i轮把截断量设为224 i保留flag[:32-i]再拼接已恢复的i个后缀字节直至还原全部 33 个字节。注意第 3 步中当恢复接近完成时所需截断量会趋近 256最后一轮需要把整个 256 字节块全部 unpad 掉实际复现时需结合目标 flag 长度微调填充值chr(224i)的取值范围。利用脚本以下 exp 即为原文档给出的完整脚本Python 2# -*- coding: utf-8 -*- from hashlib import sha256 import socket import string import itertools HOST106.75.13.64 PORT54321 sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((HOST, PORT)) def brute_force(pad, shavalue): for str in itertools.product(string.ascii_letters string.digits, repeat4): str.join(str) if sha256(str pad).hexdigest() shavalue: print str return str def choice1(): sock.send(1\n) resultsock.recv(1024).strip()[30:] sock.recv(1024).strip() return result def choice2(pad): sock.send(2\n) sock.recv(1024).strip() sock.send(pad\n) sock.recv(1024).strip() sock.recv(1024).strip() def choice3(str): sock.send(3\n) sock.recv(1024).strip() sock.send(str\n) resultsock.recv(1024).strip()[33:] sock.recv(1024).strip() return result content sock.recv(1024).strip() padcontent[12:1216] hashcontent[33:3364] sock.recv(1024).strip() sock.send(str(brute_force(pad,hash))\n) print sock.recv(1024).strip() flag_encchoice1() flag for i in range(33): a .join([a for _ in range(223)]) a a[:-1] chr(224i) for c in string.printable: print cflag choice2(a) choice2(cflag) if choice1() flag_enc: flagcflag print success:,flag break脚本关键点解读brute_force用于绕过 proof of work题目要求给出 4 个字符XXXX使sha256(XXXX proof[4:])等于给定摘要这里对ascii_letters digits做 4 位笛卡尔积爆破。flag_enc choice1()先获取原始 flag 的密文作为后续比对的基准。外层循环i从 0 到 32a a*223再改末字节为chr(224i)用于控制本次 unpad 截断224i字节从而保留flag[:32-i]。内层循环对string.printable逐个猜测c先choice2(a)完成免填充 截断准备再choice2(cflag)把猜测字节与已恢复后缀拼回 33 字节并重填充最后choice1()与flag_enc比对密文一致即说明c命中flag从末尾往前逐字节增长。最终结果flag{H4ve_fun_w1th_p4d_and_unp4d}这道题的精髓在于unpad把最后一个字节的值当成了可信的填充长度而攻击者恰好能控制它——一个缺失校验的填充实现等价于向攻击者交出任意截断密文解密结果的权限。把本题与 Padding Oracle Attack 对照阅读可以完整看到填充安全的两面校验结果泄漏可被当作 Oracle校验缺失则可被直接操控。小结分组密码必须填充即使长度恰好是块大小的整数倍如 PKCS#5/#7 会追加一整块填充以保证解填充的确定性与可校验性。五种常见填充各有取舍PKCS 类携带长度且可校验0x80 类自定界且天然免歧义ANSI X9.23 用末字节携带长度全零与空格填充则有歧义、不适合通用场景。填充的校验与否直接决定攻击面要么泄露校验结果成为 Padding Oracle要么缺失校验被攻击者操控截断长度aessss 题目即后者。aessss 是不校验 unpad 字节置换 单块 CBC 密文比对的组合利用完整 exp 可在上文直接复现flagflag{H4ve_fun_w1th_p4d_and_unp4d}。延伸阅读Padding Oracle Attack填充预言机攻击基于 PKCS5 填充校验的经典攻击含 HITCON Secret Server 系列真题CBC 模式与字节反转攻击分组模式中与填充攻击联动的另一类经典手法分组模式总览理解填充在分组工作模式中的位置AES 基础 与 DES 基础本文示例所依托的两种分组密码赞分享文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载相关推荐Handsontable Autofill 自动填充指南fill handle 拖拽填充、双击填充与钩子定制Handsontable Autofill 自动填充指南fill handle 拖拽填充、双击填充与钩子定制 导读 Autofill 是 Handsontab前端UI组件CTF Wiki 分组加密填充方式Padding全解析从 PKCS5 到不当 unpad 的逐字节攻击实战CTF Wiki 分组加密填充方式Padding全解析从 PKCS5 到不当 unpad 的逐字节攻击实战 分组加密按固定块大小处理消息但明文长度往往不文档网络安全教程PKCS7 填充crypto-js 中最常用填充方案的实现细节PKCS7 填充crypto js 中最常用填充方案的实现细节 在密码学中块加密算法如AES、TripleDES要求明文数据长度必须是块大小的整数倍。当密码学上一篇Bindu A2A 协议测试套件实战指南从目录结构、运行命令到 Fixtures、Builders 与质量规范下一篇探索动画世界的神奇画板excalidraw-animate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED

相关推荐

推荐开源项目:Sketch Material - 现代UI设计的强大工具

推荐开源项目:Sketch Material - 现代UI设计的强大工具

推荐开源项目:Sketch Material - 现代UI设计的强大工具 【免费下载链接】sketch-material Sketch material is a sketch plugin that will help you generate complex material components like tables, chips, forms etc… 项目地址: https://gitcode.com/gh_mir…

📅 2026/9/27 7:04:23
网站没人看?搞懂网页是干什么的,用免费工具救活流量

网站没人看?搞懂网页是干什么的,用免费工具救活流量

网站没人看?搞懂网页是干什么的,用免费工具救活流量 花了几千块做出来的网站,上线三天,后台日志只有你一个人访问,甚至连蜘蛛都懒得爬?别急着怪搜索引擎,大概率是你没搞懂 网页是干什么的 这一最基础却最致命的逻辑。…

📅 2026/9/27 7:04:23
长沙娱乐网站开发避坑:3种建站报价方案全拆解

长沙娱乐网站开发避坑:3种建站报价方案全拆解

长沙娱乐网站开发避坑:3种建站报价方案全拆解 改个需求建站公司拖一周,这种经历在长沙做娱乐行业的朋友圈里并不罕见。很多老板在咨询 建站报价 时,只盯着总价数字,却忽略了交付周期、需求变更响应速度以及后期维护成本这三个致命点。…

📅 2026/9/27 7:04:23
MORE NEWS

更多资讯

📰

零Rust代码支持新语言:用tree-sitter插件为sentrux添加第53种语言的完整开发指南

零Rust代码支持新语言:用tree-sitter插件为sentrux添加第53种语言的完整开发指南 【免费下载链接】sentrux Real-time architectural sensor that helps AI agents close the feedback loop, enabling recursive self-improvement of code quality. Pure Rust. 项…

📰

ng-zorro-antd Pagination 快速跳转(Quick Jumper)完全指南:从使用到源码剖析

UI组件前端 【免费下载链接】ng-zorro-antd Angular UI Component Library based on Ant Design 项目地址: https://gitcode.com/gh_mirrors/ng/ng-zorro-antd 点击查看 免费下载 导读 本文聚焦于 ng-zorro-antd(Angular UI 组件库)中 nz-p…

📰

AlgoNote 数组基础详解:线性表顺序存储、随机访问寻址与增删改查实战

教程文档知识库 【免费下载链接】AlgoNote ⛽️「算法通关手册」:从零开始的「算法与数据结构」学习教程,200 道「算法面试热门题目」,1000 道「LeetCode 题目解析」,持续更新中! 项目地址: https://gitcod…

📰

开题报告技术路线图节点经常断层?本科用智一刻避开4个误区

在开题答辩中,评审老师最喜欢看、也最容易挑出毛病的就是那张占据半页纸的“技术路线图”。 不少同学为了显得工作量饱满,用绘图软件画了密密麻麻几十个文本框,箭头横飞四处交叉。结果投影仪一放,导师直接批评:“你这…

📰

3步搞定自己怎么做卡盟网站:2026最新防黑实战

3步搞定自己怎么做卡盟网站:2026最新防黑实战 昨晚凌晨两点,手机突然震动。运营小刘发来截图,脸色铁青:“老板,咱们那个自动发卡平台挂了!首页全是赌博广告,后台密码好像也被改了!”…

📰

网课网站怎么建才不踩坑?3个最佳实践让备案通过快一倍

网课网站怎么建才不踩坑?3个最佳实践让备案通过快一倍 很多老板做网课网站,第一反应是找模板,第二反应是搞内容,但真正卡住脖子的,往往是那个让人一头雾水的备案流程。…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬