【为美好CTF献上祝福】 初探密码学
学的很浅,希望以后逆向碰上后能辨认出是什么加密方式。
先学怎么实现,再学原理
base64编码
核心思想:
将 3 字节 ( 24位 )的二进制数据分割成 4 组 6 位数据,然后将每个 6 位数据映射到 base64 索引表中的对应字符。
使用 base64 需要先定义一个索引表。
一般默认是
['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']
索引表的字符选用了 “A-Z、a-z、0-9、+、/” 64个可打印字符 (外加一个“=”作为填充符)。这是标准的 base64 协议规定。
具体转换步骤如下:
-
把待转换的字符串原始的二进制数据三个字节为一组,每个字节占 8bit ,那么共有 24 个二进制位。
-
将 24 个二进制位 6 个为一组,分成四组。(分成四个六位的组)。
-
在每组前头添加两个 0 ,每组由 6 个变为 8 个二进制位,总共 32 个二进制位,即 4 个字节。
-
如果数据长度不是 3 的倍数 用 ‘=’ 填充。
示例一: Man
举个例子,我们现在把 “Man” 转成 base64 编码。
1.Ascll 码值 : M(77) , a (97) , n (110) 。
2.把 Ascll码二进制表示。
77: 01001101
97: 01100001
110:01101110
3. 合并为 24 位 。(M、a、n 的 Ascll 码拼接在一起)
010011010110000101101110
然后 6 位 为一组,共 4 组 (拆成四组六位)
010011/010110/000101/101110
再把二进制转成 10 进制
010011 -> 19
010110 -> 22
000101 -> 5
101110 -> 46
在默认索引表里
19 -> T
22 -> W
5 -> F
46 -> u
所以 Man 的 base64 结果为 TWFu 。
示例二: A
Ascll码值: A (65)
转化成二进制: 01000001
再拆分成 4 组 6 位
010000/010
不够 6 位 , 先补 0 。
010000/010000
不够 4 组 , 缺的两组用 = 填充 。
010000 -> 16
010000 -> 16
填充 =
填充 =
在默认索引表中
16 -> Q
16 -> Q
=
=
因此 A 的 base64 结果为 QQ==
拿 AI 写了一个 C++ 脚本,能把字符串转成 base64 编码。
点击查看代码
#include <iostream>
#include <string>
static const char B64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static std::string base64_encode(const std::string& s) {
std::string out;
out.reserve(((s.size() + 2) / 3) * 4);
size_t i = 0, n = s.size();
while (i < n) {
unsigned int a = static_cast<unsigned char>(s[i++]);
unsigned int b = 0, c = 0;
bool has_b = false, has_c = false;
if (i < n) { b = static_cast<unsigned char>(s[i++]); has_b = true; }
if (i < n) { c = static_cast<unsigned char>(s[i++]); has_c = true; }
out.push_back(B64[(a >> 2) & 0x3F]);
out.push_back(B64[((a & 0x03) << 4) | (b >> 4)]);
if (has_b) {
out.push_back(B64[((b & 0x0F) << 2) | (c >> 6)]);
} else {
out.push_back('=');
}
if (has_c) {
out.push_back(B64[c & 0x3F]);
} else {
out.push_back('=');
}
}
return out;
}
int main(int argc, char** argv) {
std::string input;
if (argc > 1) { // 参数模式:把所有参数拼成一行(用空格连接)
for (int k = 1; k < argc; ++k) {
if (k > 1) input.push_back(' ');
input += argv[k];
}
} else { // 交互模式:读取一行,回车即结束
if (!std::getline(std::cin, input)) return 0;
// 处理 Windows 的 CRLF:去掉末尾的 '\r'
if (!input.empty() && input.back() == '\r') input.pop_back();
}
std::cout << base64_encode(input) << '\n';
return 0;
}
把 base64 编码转换成字符串脚本如下(可支持换表):
点击查看代码
#include <iostream>
#include <string>
#include <vector>
#include <cstring>
static const char* STD_B64 =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char* URL_B64 =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
static bool build_map(const std::string& alphabet, signed char map256[256], char pad) {
if (alphabet.size() != 64) return false;
for (int i = 0; i < 256; ++i) map256[i] = -1;
// pad 不能出现在字母表中
for (size_t i = 0; i < alphabet.size(); ++i) {
unsigned char ch = static_cast<unsigned char>(alphabet[i]);
if (ch == static_cast<unsigned char>(pad)) return false;
if (map256[ch] != -1) return false; // 重复字符
map256[ch] = static_cast<signed char>(i);
}
return true;
}
static bool is_space(unsigned char c) {
if (c == ' ') return true;
if (c == '\t') return true;
if (c == '\n') return true;
if (c == '\r') return true;
return false;
}
static bool base64_decode_custom(const std::string& input,
const std::string& alphabet,
char pad,
std::string& out) {
signed char map256[256];
if (!build_map(alphabet, map256, pad)) {
std::cerr << "错误:无效的字母表(长度必须为64且不能包含填充字符)。\n";
return false;
}
// 过滤:移除空白,其余必须是字母表或 pad
std::string s;
s.reserve(input.size());
for (size_t i = 0; i < input.size(); ++i) {
unsigned char u = static_cast<unsigned char>(input[i]);
if (is_space(u)) continue;
if (u == static_cast<unsigned char>(pad)) {
s.push_back(static_cast<char>(u));
} else if (map256[u] != -1) {
s.push_back(static_cast<char>(u));
} else {
std::cerr << "错误:检测到非 Base64 字符:0x"
<< std::hex << static_cast<int>(u) << std::dec << "\n";
return false;
}
}
if (s.size() % 4 != 0) {
std::cerr << "错误:去空白后的长度不是4的倍数。\n";
return false;
}
out.clear();
if (!s.empty()) out.reserve((s.size() / 4) * 3);
size_t i = 0;
while (i < s.size()) {
unsigned char c0 = static_cast<unsigned char>(s[i++]);
unsigned char c1 = static_cast<unsigned char>(s[i++]);
unsigned char c2 = static_cast<unsigned char>(s[i++]);
unsigned char c3 = static_cast<unsigned char>(s[i++]);
if (c0 == static_cast<unsigned char>(pad) || c1 == static_cast<unsigned char>(pad)) {
std::cerr << "错误:前两位不允许为填充字符。\n";
return false;
}
int v0 = map256[c0];
int v1 = map256[c1];
if (v0 < 0 || v1 < 0) return false;
unsigned char b0 = static_cast<unsigned char>((v0 << 2) | (v1 >> 4));
out.push_back(static_cast<char>(b0));
if (c2 == static_cast<unsigned char>(pad)) {
// "xx==": 仅 1 字节
if (c3 != static_cast<unsigned char>(pad)) {
std::cerr << "错误:'=' 使用不合法。\n";
return false;
}
break;
} else {
int v2 = map256[c2];
if (v2 < 0) return false;
unsigned char b1 = static_cast<unsigned char>(((v1 & 0x0F) << 4) | (v2 >> 2));
out.push_back(static_cast<char>(b1));
if (c3 == static_cast<unsigned char>(pad)) {
// "xxx=": 产生两字节
break;
} else {
int v3 = map256[c3];
if (v3 < 0) return false;
unsigned char b2 = static_cast<unsigned char>(((v2 & 0x03) << 6) | v3);
out.push_back(static_cast<char>(b2));
}
}
}
return true;
}
int main(int argc, char** argv) {
std::string alphabet = STD_B64; // 默认标准表
char pad = '=';
std::string b64; // 待解码文本
// 解析参数:支持 -t <表> -p <填充符> --url --std
for (int i = 1; i < argc; ++i) {
std::string a = argv[i];
if (a == "-t" && i + 1 < argc) {
alphabet = argv[++i];
} else if (a == "-p" && i + 1 < argc) {
std::string p = argv[++i];
if (p.size() != 1) {
std::cerr << "错误:-p 需要单个字符作为填充符。\n";
return 2;
}
pad = p[0];
} else if (a == "--url") {
alphabet = URL_B64;
} else if (a == "--std") {
alphabet = STD_B64;
} else {
if (!b64.empty()) b64.push_back(' ');
b64 += a;
}
}
if (b64.empty()) {
if (!std::getline(std::cin, b64)) return 0;
if (!b64.empty() && b64.back() == '\r') b64.pop_back(); // 兼容 Windows CRLF
}
std::string out;
bool ok = base64_decode_custom(b64, alphabet, pad, out);
if (!ok) return 1;
std::cout.write(out.data(), static_cast<std::streamsize>(out.size()));
std::cout << '\n';
return 0;
}
base64换表
注意:base64 的索引表是可以换的,一般是对原索引表重新排序。
原表是
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
换表可能是
WHydo3sThiS7ABLElO0k5trange+CZfVIGRvup81NKQbjmPzU4MDc9Y6q2XwFxJ/
MD5加密算法
定义:
MD5(Message Digest Algorithm 5)是由Ronald Rivest于1991年设计的密码散列函数,可将任意长度数据转换为128位(16字节)的固定长度散列值。曾广泛应用于数据完整性校验和密码存储领域。
特性: 1.始终生成128位哈希值
2.本质是哈希,具有不可逆性,无法从哈希值反推原始数据(但能爆破)。
处理流程:
- 填充数据:
对需要处理的消息(字符串)进行数据填充,使字符串的长度对512取模得448,设字符串长度为X,即满足 X mod 512=448 。根据此公式得出需要填充的数据长度。
填充方法:在消息后面进行填充,填充第一位为1,其余为0。
2.添加消息长度
在第一步的基础上再填充上原字符串的长度,可用来进行的存储长度为 64 位。如果字符串长度大于 \(2^{64}\) ,即使用其低于 64 位的值。 即(消息长度 对 \(2^{64}\) 取模)。
此步骤结束后,最终消息长度就是 \(512\) 的整数倍。
3.数据处理
需要用到四个常数
A = 0x67452301, B = 0x0EFCDAB89, C = 0x98BADCFE, D = 0x10325476;
以及四个函数
F(X,Y,Z)=(X & Y) | ((~X) & Z)
G(X,Y,Z)=(X & Z) | (Y & (~Z))
H(X,Y,Z)=X ^ Y ^ Z
I(X,Y,Z)=Y ^ (X | (~Z))
把消息分以512位为一分组进行处理,每一个分组进行4轮变换,以上面所说4个常数为起始变量进行计算,重新输出4个变量,以这4个变量再进行下一分组的运算,如果已经是最后一个分组,则这4个变量为最后的结果,即MD5值。
TEA加密
一种微型加密算法。
加密函数大致如下:
void Encrypt(long* EntryData, long* Key)
{
//分别加密数组中的前四个字节与后4个字节,4个字节为一组每次加密两组
unsigned long x = EntryData[0];
unsigned long y = EntryData[1];
unsigned long sum = 0;
unsigned long delta = 0x9E3779B9;
//总共加密32轮
for (int i = 0; i < 32; i++)
{
sum += delta;
x += ((y << 4) + Key[0]) ^ (y + sum) ^ ((y >> 5) + Key[1]);
y += ((x << 4) + Key[2]) ^ (x + sum) ^ ((x >> 5) + Key[3]);
}
//最后加密的结果重新写入到数组中
EntryData[0] = x;
EntryData[1] = y;
}

浙公网安备 33010602011771号