简单但是需要记录.... 做题的时候就能直接看...

[TOC]

# 单表替换:

# Caesar:

字符移位的内容...

注:大小写 / 数字 / 符号 可能偏移不同。

def change(c,i):
    num=ord(c)
    if(num>=33 and num<=126):
        num=33+(num+i-33)%(94)#126-33=93
    return chr(num)
def kaisa_jiAmi(string,i):
    string_new=''
    for s in string:
        string_new+=change(s,i)
    print(string_new)
    return string_new
#本题有种暴力解密感觉
def kaisa_jiEmi(string):
    for i in range(0,94):
        print('第'+str(i+1)+'种可能:',end='  ')
        #区别在于 string 是该对象原本就是字符串类型,而 str () 则是将该对象转换成字符串类型。
        kaisa_jiAmi(string,i)
    
#你要知道 input 输入的数据类型都是 string  
def main():
    print('请输入操作,注意不是平常26种:')
    choice=input('1:凯撒加密,2:凯撒穷举解密.请输入1或2:')
    if choice=='1':
        string=input('请输入需要加密字符串: ')
        num=int(input('请输入需要加密的KEY: '))
        kaisa_jiAmi(string,num)
    elif choice=='2':
        string=input('请输入需要解密字符串: ')
        kaisa_jiEmi(string)
    else:
        print('输入错误,请重试')
        main()
    
if __name__=='__main__':
    main()

# Atbash Cipher:

即明文对应 26 个字母里倒叙的密文

明文:A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

密文:Z Y X W V U T S R Q P O N M L K J I H G F E D C B A

# Easy Replace:

随机替换:http://quipquip.com/

# Affair :

  • 逐位加密: E (x) = (ax+b) mod m
    • x 明文,a 和 m 互质,m 是编码系统中字母数目
  • 解密: D (y) = a-1 (y-b) mod m

# 多表替换:

# Playfair:

# 原理:

  1. 选取一串英文字母,除去重复出现的字母,将剩下的字母逐个逐个加入 5 × 5 的矩阵内,剩下空间使用还没有加入的英文字母依次加入。注意 : 去除 q 或者 i 和 j 是同一个
  2. 把加密的明文分成 2 个一组,如果组内字母一样,就把 X 或 Q 加到这组的第一个字母以后再重新分组。剩下一个字母的话也是如此
  3. 每组里面,找到两个字母的位置
    • 两个字母不同行不同列,就在矩阵里找另外两个字母,使这四个字母成为一个长方形的四个角
    • 两个字母同行,就取这两个字母右边的字母(若在最右边,取最左边
    • 两个字母同列,就取两个字母下边的字母(同理
  4. 新找到的两个字母就是加密结果

# 例子:

密钥:playfair example

P L A Y F

I R E X M

B C D G H

K N O Q S

T U V W Z

明文:Hide the gold in the tree stump

HI DE TH EG OL DI NT HE TR EX ES TU MP

加密:BM OD ZB XD NA BE KU DM UI XM MO UV IF

# Polybius:

密文和明文字符对应 2:1

常用密码表:

12345
1ABCDE
2FGHI/JK
3LMNOP
4QRSTU
5VWXYZ

脚本:# 对密文五个字符进行排列爆破

import itertools
key = []
cipher = ""
for i in itertools.permutations('abcde', 5):
    key.append(''.join(i))
for now_key in key:
    solve_c = ""
    res = ""
    for now_c in cipher:
        solve_c += str(now_key.index(now_c))
    for i in range(0,len(solve_c),2):
        now_ascii = int(solve_c[i])*5+int(solve_c[i+1])+97
        if now_ascii>ord('i'):
            now_ascii+=1
        res += chr(now_ascii)
    if "flag" in res:
        print(now_key,res)

# Vigenere:

二维 Caesar

已知 key 解码脚本:

from itertools import cycle
keyword = ''
cipher = ''
def Vigenere_dec(ct, k):
    shifts = cycle(alphabet.index(c) for c in k)
    pt = ''
    for c in ct.lower():
        if c not in alphabet: 
            next(shifts)
            pt += c
            continue
        pt += alphabet[(alphabet.index(c) - next(shifts)) % len(alphabet)]
    return pt
plain = Vigenere_dec(cipher, keyword)

# 破解:词频分析

主要是由于 Vigenere 密码的密钥周期循环.

# Kasiski Test:

可以找到密文序列中重复的片段,密钥长度很大概率是这个距离的因子

# The index of Coincidence Test

定义:长度为 n 的字符串 s , 他的 index of coincidence 就是在 s 里面随机选 2 个字符,并且相同的概率 记作 IndCo (s)

# 公式:

设 N<sub>i</sub> 是字母 i 在字符串中出现次数 那么

IndCo(s)=Σi=025CNi2Clen(s)2=1n(n1)Σi=025Ni(Ni1)IndCo(s) = \frac{\Sigma^{25}_{i=0}C_{N_i}^2}{C^2_{len(s)}}=\frac{1}{n(n-1)}\Sigma^{25}_{i=0}N_i(N_i-1)

# 用法:

猜测长度 k 把第 i+jk 位密文作为一组。这些组中的字母频率分布规律和普通英文文本一样。

每个子字符串计算出 IndCo (s<sub>i</sub>) 的均值,越趋近于 0.0685,密钥长度越正确,越趋近于 0.0385 越随机。密文越长,密钥越短效果越好

def IndCo(s):
    # 计算字符串的重合指数(所有字母出现频率的平方和)
    # 输入 s 
    # 输出 重合指数
    alpha = 'abcdefghijklmnopqrstuvwxyz'
    freq = {}
    for i in alpha:
        freq[i] = 0
    for i in s:
        freq[i] =  freq[i] + 1
    index = 0
    for i in alpha:
        index = index + (freq[i]*(freq[i] - 1 )) / (len(s) * (len(s) - 1 ))
    return index
def get_keylen(c):
    # 求出最符合统计学的 m,n 的最小公倍数,方法通过爆破足够大的周期样本,观察成倍出现的周期
    # 计算方法是解出每一个子密文段的重合指数然后求平均值 在和最佳重合指数相减 误差小于 0.01
    # 输入:密文
    # 输出:公共周期列表
    keylen = []
    for i in range(1,100):
        average_index = 0
        for j in range(i):
            s = ''.join(c[j+i*x] for x in range(0,len(c)//i))
            index = IndCo(s)
            average_index+=index
        average_index = average_index/i - best_index
        if abs(average_index)<0.01:
            keylen.append(i)
    return keylen
# Trivial Method

如果密文长度越长,那么每组子字符串都会出现这种频率,并且很大概率出现次数最多的就是字母 e

def RecoverKeyword_1(ct, kl):
    '''
    Recover the keyword according to the most frequent letters.
    :param str ct: The ciphertext.
    :param int kl: The key length.
    :return: The recovered keyword.
    :rtype: str
    '''
    keyword = ''
    subs = [ct[i::kl] for i in range(kl)]
    for s in subs:
        frequency = [s.count(c) for c in alphabet]
        most_fqc = max(frequency)
        keyword += alphabet[(frequency.index(most_fqc) - 4) % len(alphabet)]
    return keyword
# Nontrivial Method
# Chi-squared Statistic

猜解解密后的明文是否具有文章有序特征。

方差统计:

δ2(C,E)=Σi=025(CiEi)2Ei\delta^2(C,E)=\Sigma^{25}_{i=0}\frac{(C_i-E_i)^2}{E_i}

C<sub>i</sub> 是字母出现次数 ,E<sub>i</sub> 是期望出现次数。

# 代码 1:
from string import ascii_lowercase as alphabet
LETTER_FREQUENCY = {  # From https://en.wikipedia.org/wiki/Letter_frequency.
    'e': 0.12702,'t': 0.09056,'a': 0.08167,'o': 0.07507,'i': 0.06966,'n': 0.06749,'s': 0.06327,'h': 0.06094,'r': 0.05987,'d': 0.04253,'l': 0.04025,'c': 0.02782,'u': 0.02758,'m': 0.02406,'w': 0.02360,'f': 0.02228,'g': 0.02015,'y': 0.01974,'p': 0.01929,'b': 0.01492,'v': 0.00978,'k': 0.00772,'j': 0.00153,'x': 0.00150,'q': 0.00095,'z': 0.00074
}
def ChiSquared(s):
    '''
    Calculate the `Chi-squared Statistic`.
    :param str s: The string to be analysed.
    :return: The `Chi-squared Statistic` of the string.
    :rtype: float
    '''
    f = lambda c: LETTER_FREQUENCY[c] * len(s)
    sum = 0
    for c in alphabet:
        if c in LETTER_FREQUENCY :
           sum += (s.count(c) - f(c))**2 / f(c)
    return sum
def RecoverKeyword_2(ct, kl):
    '''
    Recover the keyword according to the `Chi-squared Statistic`.
    :param str ct: The ciphertext.
    :param int kl: The key length.
    :return: The recovered keyword.
    :rtype: str
    '''
    keyword = ''
    subs = [ct[i::kl] for i in range(kl)]
    for s in subs:
        chi_squareds = []
        for shift in range(len(alphabet)):  # Try all possible shifts.
            shifted_s = ''.join(\
                alphabet[(alphabet.index(c) - shift) % len(alphabet)]\
                for c in s)
            chi_squareds.append((shift, ChiSquared(shifted_s)))
        keyword += alphabet[min(chi_squareds, key=lambda x: x[1])[0]]
    return keyword
# 代码 2:
from itertools import *
from string import printable
alphabet = list(range(256))
LETTER_FREQUENCY = {  # From https://en.wikipedia.org/wiki/Letter_frequency.
    'e': 0.12702,'t': 0.09056,'a': 0.08167,'o': 0.07507,'i': 0.06966,'n': 0.06749,'s': 0.06327,'h': 0.06094,'r': 0.05987,'d': 0.04253,'l': 0.04025,'c': 0.02782,'u': 0.02758,'m': 0.02406,'w': 0.02360,'f': 0.02228,'g': 0.02015,'y': 0.01974,'p': 0.01929,'b': 0.01492,'v': 0.00978,'k': 0.00772,'j': 0.00153,'x': 0.00150,'q': 0.00095,'z': 0.00074
}
def IndCo(s):
    N = len(s)
    frequency = [s.count(c) for c in alphabet]
    return sum(i**2 - i for i in frequency) / (N**2 - N)
def CalKeyLength(s):
    res = []
    for kl in range(2, 38):
        subs = [s[i::kl] for i in range(kl)]
        if sum(IndCo(si) for si in subs) / kl > 0.06:
            if all(map(lambda x: kl % x, res)):
                res.append(kl)
    return res
def score(s):
    score = 0
    for c in s.lower():
        if c in LETTER_FREQUENCY:
            score += LETTER_FREQUENCY[c]
    return score
def RecoverKey(ct, kl):
    key = b''
    subs = [ct[i::kl] for i in range(kl)]
    for s in subs:
        scores = []
        for xor in range(256):
            xored_s = ''.join(chr(c ^ xor) for c in s)
            if all(c in printable for c in xored_s):
                scores.append((xor, score(xored_s)))
        key += bytes([max(scores, key=lambda x: x[1])[0]])
    return key
def Vigenere_dec(cipher, key):
    keyCircle = cycle(key)
    pt = ''
    for c in cipher:
        pt += chr(c ^ next(keyCircle))
    return pt
def main():
    cipher = ''
    kls = CalKeyLength(cipher)
    print(f"All probable key length: {kls}")
    for kl in kls:
        key = RecoverKey(cipher, kl)
        print(f"Key: {key}")
        print(Vigenere_dec(cipher, key))
if __name__ == '__main__':
    main()

# 扩展:

# 汉明距离:

正常英文字母平均距离是 2~3,任意字符平均距离为 4

同一个 key 位加密的两字符,异或结果也是其明文异或的结果

汉明距离指两个 01 串中相应比特位不同的数量

# 代码:
import base64
import string
def bxor(a, b):     # xor two byte strings of different lengths
    if len(a) > len(b):
        return bytes([x ^ y for x, y in zip(a[:len(b)], b)])
    else:
        return bytes([x ^ y for x, y in zip(a, b[:len(a)])])
def hamming_distance(b1, b2):
    differing_bits = 0
    for byte in bxor(b1, b2):
        differing_bits += bin(byte).count("1")
    return differing_bits
def score(s):
    freq = {}
    freq[' '] = 700000000
    freq['e'] = 390395169
    freq['t'] = 282039486
    freq['a'] = 248362256
    freq['o'] = 235661502
    freq['i'] = 214822972
    freq['n'] = 214319386
    freq['s'] = 196844692
    freq['h'] = 193607737
    freq['r'] = 184990759
    freq['d'] = 134044565
    freq['l'] = 125951672
    freq['u'] = 88219598
    freq['c'] = 79962026
    freq['m'] = 79502870
    freq['f'] = 72967175
    freq['w'] = 69069021
    freq['g'] = 61549736
    freq['y'] = 59010696
    freq['p'] = 55746578
    freq['b'] = 47673928
    freq['v'] = 30476191
    freq['k'] = 22969448
    freq['x'] = 5574077
    freq['j'] = 4507165
    freq['q'] = 3649838
    freq['z'] = 2456495
    score = 0
    string=bytes.decode(s)
    for c in string.lower():
        if c in freq:
            score += freq[c]
    return score
def break_single_key_xor(b1):
    max_score = 0
    english_plaintext = 0
    key = 0
    for i in range(0,256):
        b2 = [i] * len(b1)
        try:
            plaintext = bxor(b1, b2)
            pscore = score(plaintext)
        except Exception:
            continue
        if pscore > max_score or not max_score:
            max_score = pscore
            english_plaintext = plaintext
            key = chr(i)
    return key
text = ''
with open(r" ", "r") as f:
    for line in f:
        text += line
b = base64.b64decode(text)
normalized_distances = []
for KEYSIZE in range(2, 40):
    # 我们取其中前 6 段计算平均汉明距离
    b1 = b[: KEYSIZE]
    b2 = b[KEYSIZE: KEYSIZE * 2]
    b3 = b[KEYSIZE * 2: KEYSIZE * 3]
    b4 = b[KEYSIZE * 3: KEYSIZE * 4]
    b5 = b[KEYSIZE * 4: KEYSIZE * 5]
    b6 = b[KEYSIZE * 5: KEYSIZE * 6]
    b7 = b[KEYSIZE * 6: KEYSIZE * 7]
    normalized_distance = float(
        hamming_distance(b1, b2) +
        hamming_distance(b2, b3) +
        hamming_distance(b3, b4) +
        hamming_distance(b4, b5) +
        hamming_distance(b5, b6) 
    ) / (KEYSIZE * 5)
    normalized_distances.append(
        (KEYSIZE, normalized_distance)
    )
normalized_distances = sorted(normalized_distances, key=lambda x: x[1])
for KEYSIZE, _ in normalized_distances[:5]:
    block_bytes = [[] for _ in range(KEYSIZE)]
    for i, byte in enumerate(b):
        block_bytes[i % KEYSIZE].append(byte)
    keys = ''
    for bbytes in block_bytes:
        keys += break_single_key_xor(bbytes)
    key = bytearray(keys * len(b), "utf-8")
    plaintext = bxor(b, key)
    print("keysize:", KEYSIZE)
    print("key is:", keys, "n")
    s = bytes.decode(plaintext)
    print(s)

# 例题:2021BlueHat Classical:

from random import randint
from secret import l1, l2, text, key, flag
# text is a plain English text which only consists of lowercase letters (without any symbol)
table = 'abcdefghijklmnopqrstuvwxyz'
assert key in text
assert l1 * l2 < 100
k1 = []
k2 = []
fib = [0, 1]
modulus = randint(12345,6789010)
for i in range(1 << 16):
    fib.append((fib[-1] + fib[-2]) % modulus)
for i in range(l1):
    k1.append(fib[randint(0, 1 << 16)] % 26)
for i in range(l2):
    k2.append(fib[randint(0, 1 << 16)] % 26)
c = ''.join(table[((ord(x) - 97) * (k1[i % l1]) + k2[i % l2]) % 26] for i, x in enumerate(text))
from Crypto.Cipher import AES
import base64
aes = AES.new(key.encode(), AES.MODE_CBC, b'\0' * 16)
print(c)
print(base64.b64encode(aes.encrypt(flag + b'\0' * (16 - len(flag) % 16))))

直接因为 l1*l2 <100 因此也可以用词频分析去写。

import gmpy2
f = open(r'c.txt','r')
c = f.read()
f.close()
# 我们已经知道,维吉尼亚密码可以被分解为若干组平移密码来破译,
# 而一个明文足够长的平移密码的重合指数接近 0.0687。
# 换言之,如果我们选取某个 l 值,使得分组后的密文的重合指数接近 0.065,
# 则说明选取的 t 值与密钥的长度是一致的。
best_index = 0.065
sum = 0
dic_index = {'a': 0.08167,'b': 0.01492,'c': 0.02782,'d':0.04253,'e': 0.12702,'f':0.02228,'g': 0.02015,'h':0.06094,'i':0.06966,'j':0.00153,'k':0.00772,'l':0.04025,'m':0.02406,'n':0.06749,'o':0.07507,'p':0.01929,'q':0.00095,'r':0.05987,'s':0.06327,'t':0.09056,'u':0.02758,'v':0.00978,'w':0.02360,'x':0.00150,'y':0.01974,'z':0.00074}
def IndCo(s):
    # 计算字符串的重合指数(所有字母出现频率的平方和)
    # 输入 s 
    # 输出 重合指数
    alpha = 'abcdefghijklmnopqrstuvwxyz'
    freq = {}
    for i in alpha:
        freq[i] = 0
    for i in s:
        freq[i] =  freq[i] + 1
    index = 0
    for i in alpha:
        index = index + (freq[i]*(freq[i] - 1 )) / (len(s) * (len(s) - 1 ))
    return index
def IndCo_m(s):
    # 计算明文 s 中的各字母频率和英文字母中的频率吻合程度
    # 输入:明文 s
    # 输出:吻合程度
    alpha = 'abcdefghijklmnopqrstuvwxyz'
    freq = {}
    for i in alpha:
        freq[i] = 0
    for i in s:
        freq[i] += 1
    index = 0
    for i in alpha:
        index += freq[i] / len(s) * dic_index[i]
    return index
def get_keylen(c):
    # 求出最符合统计学的 m,n 的最小公倍数,方法通过爆破足够大的周期样本,观察成倍出现的周期
    # 计算方法是解出每一个子密文段的重合指数然后求平均值 在和最佳重合指数相减 误差小于 0.01
    # 输入:密文
    # 输出:公共周期列表
    keylen = []
    for i in range(1,100):
        average_index = 0
        for j in range(i):
            s = ''.join(c[j+i*x] for x in range(0,len(c)//i))
            index = IndCo(s)
            average_index+=index
        average_index = average_index/i - best_index
        if abs(average_index)<0.01:
            keylen.append(i)
    return keylen
keylen = get_keylen(c)
print(keylen)
#____________________________得到 keylen_____________________________#
#____________________________爆破 flag———————————————————————————————#
def decrypt(c,i,j):
    alpha = 'abcdefghijklmnopqrstuvwxyz'
    m = ''
    for x in c:
        m += alpha[((alpha.index(x)-j)*gmpy2.invert(i,26))%26]
    return m 
def get_key(c):
    # 得到一个密文段的单个字符 key .i .j
    # 暴力枚举,找到最符合的
    # 输入:密文段
    # 输出:i,j
    for i in range(26):
        if gmpy2.gcd(i,26)!= 1 :
            continue
        for j in range(26):
            m = decrypt(c,i,j)
            index = IndCo_m(m)
            if abs(index-0.065)<0.01:
                return (i,j)
def get_all_key(s,keylen):
    # 得到一个周期内所有密文段的 key
    # 输入:原密文,周期
    # 输出:无
    for i in range(keylen):
        temps = ''.join([s[i+x*keylen] for x in range(0,len(s)//keylen)])
        print(get_key(temps))
get_all_key(c,keylen[0])
# 于是就可以分析得到 ka = []  kb= []
# 例如:(23, 9)(25, 21)(21, 9)(3, 10)(19, 16)(23, 3)(25, 22)(21, 13)(3, 14)(19, 21)(23, 15)(25, 9)(21, 21)(3, 9)(19, 10)(23, 16)(25, 3)(21, 22)(3, 13)(19, 14)(23, 21)(25, 15)(21, 9)(3, 21)(19, 9)(23, 10)(25, 16)(21, 3)(3, 22)(19, 13)(23, 14)(25, 21)(21, 15)(3, 9)(19, 21)(23, 9)(25, 10)(21, 16)(3, 3)(19, 22)(23, 13)(25, 14)(21, 21)(3, 15)(19, 9)(23, 21)(25, 9)(21, 10)(3, 16)(19, 3)(23, 22)(25, 13)(21, 14)(3, 21)(19, 15)
# 那么得到就是 k1=[23,25,21,3,19],k2=[9,21,9,10,16,3,22,13,14,21,15]
import base64
from Crypto.Cipher import AES
plaintext = ''
k1 = [23,25,21,3,19]
k2 = [9,21,9,10,16,3,22,13,14,21,15]
l1 = len(k1)
l2 = len(k2)
alpha='abcdefghijklmnopqrstuvwxyz'
for i in range(len(c)):
    plaintext+=alpha[((alpha.index(c[i])-k2[i%l2])*gmpy2.invert(k1[i%l1],26))%26]
a = b'XpOY4zBvK6h//jAgIraYzBBK1lXz9pw7DxXGt6XoODZrSUCpjFzgw5pCo3ffclKM'
cipher = base64.b64decode(a)
for i in range(len(plaintext)-16):
    key = plaintext[i:i+16]
    aes = AES.new(key.encode(), AES.MODE_CBC, b'\0' * 16)
    flag = aes.decrypt(cipher)
    if b'flag' in flag:
        print(flag)
        print(key)
        f = open(r'out.txt','w')
        f.write(plaintext)
        break

# Nihilist

只包含 1~5,密文的长度是偶数的

又称为关键字密码:明文 + 关键字 = 密文

利用密钥构造棋盘矩阵(类似 Polybius)

# Hill

每个字母都当作 26 进制的数字,一串字母当作 n 维向量,和一个 n x n 的矩阵相乘 再把结果 mod 26,矩阵计算即可

# 编码

# 进制转换

# 二进制

  • 01 字符串,可能 8 位,也可能 7 位(省略第一个 0),通常直接转 ascii 即可(注意逆序)
  • 可用来表示黑白像素点(二维码或其他),例题:[INSHack2018] Self Congratulation

image-20210121131441867

  • 可用来画图,例题:[XCTF 3rd-RCTF-2017] message

img

  • 可用来表示摩斯码(01 表 .-
  • 可用来表示培根加密(01 表 AB,五个一组)
  • 一切只由两种符号构成的内容都可以想到二进制

# 十进制

  • 很长的十进制数字,libnum 库
import libnum
s = 'flag{2333}'
print libnum.s2n(s)
#483680648326664261153661
n = 483680648326664261153661
print libnum.n2s(n)
#flag{2333}
  • 可转 bin 或 hex

# 十六进制

  • 0-9a-f 组成,一般会给十六进制的 flag 字符串,转换成 ascii 即可,牢记 flag 的 hex 表达 66 6c 61 67 (注意逆序)
  • winhex、010 等工具显示的数据都为十六进制,要记各种常考文件的文件头尾 hex 表示形式,网站
  • 给大量的 hex 字符串,可丢进 cyberchef 或十六进制编辑器,查看内容

# base

# base16

16 进制,无 = 填充

# base32

A-Z2-7 共 32 个可打印字符组成,末尾最多 6 个 =

# base36

A-Z0-9 共 36 个可打印字符组成,无 = 填充

# base58

和 base64 相比,去除了有歧义字符 0(零),O(大写字母 O),I(大写字母 i),l(小写字母 L)以及特殊符号 +/

# base62

和 base64 相比,去除了特殊符号 +/

# base64

A-Za-z0-9+/ 共 64 个可打印字符组成,对应 0-63,末尾最多 2 个 =

img

# 原理

参考:https://zhuanlan.zhihu.com/p/111700349

编码时,每 3 个字节一组,共 8bit*3=24bit,划分成 4 组,即每 6bit 代表一个编码后的索引值

img

如果编码后内容的长度不是 4 的整数倍,需要在末尾补充 = 使其达到 4 的整数倍的长度

img

img

解码时,先删去 = ,再每 8 位一组,剩余的部分舍弃,转换成 ascii 码

通过上面了两个图,我们可以发现

  • 每有一个 = ,就会有两位 0 舍弃

即使将多余的 0 改为其他内容,也不会影响解码结果,这样就构成了 base64 隐写

# base64 隐写

以字母 c 为例

  • 编码:011000 110000 → Yw==
  • 隐写:011000 111111 → Y/==

通过上面的分析,我们可以发现每个 = 最多只有 2 位的隐写空间,而每个 base64 编码最多只有 2 个 = ,这也就意味着想要隐藏一句完整的话或者隐藏内容比较多的信息,就需要大量的 base64 编码,将想要隐写的内容转换成 01 字符串,再按顺序塞进每个 = 对应的多余位置,即可完成隐写

YTUwZDllYTBhNTk5OWI0NmVlZmIzN2MzMDMzODlkZmR=
OTk4ODO0OmE4NWU0OTg1OTcyNjk1OzZlOWQ2ZDk5OWO=
NmZjOGJ3ZGUyODBmYmJ4ODYwZjMzZGU5NWUyZjdlNmJ=
ZDBhNDaxMzQ4NWRkZDM3a2a4ZjQwZTE0OWazM2IxMTa=
NzNlOTAzMGUwZDJwMjM3NzM0NjRmM2RhYzYxNDMxYjJ=
OTIwNzJkN2YwN2Y1Y2O0Y2Y2OjAxOTE5OGQ2NDE3NDO=
ZmIyOWIxY2Y0YTI2ZmU0Y2I2MGZiNTIzOTcxM2ZhNjT=
OWQ3NDRkOWEzNWI4NTYwYzIwYjI1ODA3Y2I5Njg4M2Q=
MjI4Z2Z3OTM0MjllMjM4NTM0NjZlNjNiZmUxZDJiZGZ=
YmQ2YjFlNjM1ZWY4YmQ4N2W0MWQ1NGIxODdkY2QwMjW=
ZmFiMTE4OTUyOTdkNjc0NmIwODAwMDdmYWE4MTFkZGU=
NDYzNGQ3NTMwNDZlMGJlNzhmNTMwM2MyMjk3ZjgwNzd=
MTUyZWNjYTViN2M5YWI1NjYzY2VlYTY1MzRxZmE0MjR=
ZGM3Yjk5YWQxNmYwMGU5MWVhYWQ4MDI1MGVlNjNjODC=
ZTc3NDg2MWZzODAwZjM1NTM3MGFlNDRjNDk0NjUxZmZ=
YTRlNjE3Y2E4ZWEwYWNkZTQ1M2QxYjM2YWFmN2U1MjD=

以上内容隐写了 flag

import re
import base64
b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
# ccc.txt 为待解密的 base64 隐写字符串所在的文件
f = open('ccc.txt','r')
base64str = f.readline()
# pattern2 用于匹配两个等号情况时,等号前的一个字符
# pattern1 用于匹配一个等号情况时,等号前的一个字符
pattern2 = r'(\S)==$'
pattern1 = r'(\S)=$'
# 提取后的隐写二进制字符加入 binstring 中
binstring = ''
# 逐行读取待解密的 base64 隐写字符串,逐行处理
while(base64str):
    # 先匹配两个等号的情况,如果匹配不上,再配置一个等号的情况
    # 如果无等号,则没有隐藏,无需处理
    if re.compile(pattern2).findall(base64str):
        # mstr 为等号前的一个字符,该字符为隐写二进制信息所在的字符
        mstr = re.compile(pattern2).findall(base64str)[0]
        # 确认 mstr 字符对应的 base64 二进制数,赋值给 mbin
        mbin = bin(b64chars.find(mstr))
        # mbin 格式如 0b100,mbin [0:2] 为 0b
        # mbin [2:].zfill (6) 为将 0b 后面的二进制数前面补 0,使 0b 后面的长度为 6
        mbin2 = mbin[0:2] + mbin[2:].zfill(6)
        # 两个等号情况隐写了 4 位二进制数,所以提取 mbin2 的后 4bit
        # 赋值给 stegobin,这就是隐藏的二进制信息
        stegobin = mbin2[-4:]
        binstring += stegobin
    elif re.compile(pattern1).findall(base64str):
        mstr = re.compile(pattern1).findall(base64str)[0]
        mbin = bin(b64chars.find(mstr))
        mbin2 = mbin[0:2] + mbin[2:].zfill(6)
        # 一个等号情况隐写了 2 位二进制数,所以提取 mbin2 的后 2bit
        stegobin = mbin2[-2:]
        binstring += stegobin
    base64str = f.readline()
# stegobin 将各行隐藏的二进制字符拼接在一起
# 从第 0 位开始,8bit、8bit 处理,所以 range 的步进为 8
for i in range(0,len(binstring),8):
    # int (xxx,2),将二进制字符串转换为 10 进制的整数,再用 chr () 转为字符
    print(chr(int(binstring[i:i+8],2)),end='')

脚本取自:https://www.jianshu.com/p/f1f4e10ad10e

# base85

base85 的种类比较多,没有比较固定的特征,主要就是特殊字符比较多

0k"L&+Cno%AmoakA7crmF(?D*@<5sk+CT<
k-CE:O-/RjYNV.$k_cJ(P*4soVWP4M
jLq5JV7l?1N95qYk*W>sG^'

# base91

由(0-9,a-z,A-Z,!#$%&()*+,./:;<=>?@[]^_`{|}~")共 91 个可打印字符组成

# base92

由(0-9,a-z,A-Z,!#$%&()*+,./:;‘<=>?@[]^_`{|}~")共 92 个可打印字符组成(比 base91 多了个

# base100

编码后的内容由 emoji 构成

 → flag

# base128

不常见,作为了解,编码表为 ascii 码表

# 其他

# aaencode

编码后的内容为颜文字

゚ω゚ノ= /`m´)ノ ~┻━┻   //*´∇`*/ ['_']; o=(゚ー゚)  =_=3; c=(゚Θ゚) =(゚ー゚)-(゚ー゚); (゚Д゚) =(゚Θ゚)= (o^_^o)/ (o^_^o);(゚Д゚)={゚Θ゚: '_' ,゚ω゚ノ : ((゚ω゚ノ==3) +'_') [゚Θ゚] ,゚ー゚ノ :(゚ω゚ノ+ '_')[o^_^o -(゚Θ゚)] ,゚Д゚ノ:((゚ー゚==3) +'_')[゚ー゚] }; (゚Д゚) [゚Θ゚] =((゚ω゚ノ==3) +'_') [c^_^o];(゚Д゚) ['c'] = ((゚Д゚)+'_') [ (゚ー゚)+(゚ー゚)-(゚Θ゚) ];(゚Д゚) ['o'] = ((゚Д゚)+'_') [゚Θ゚];(゚o゚)=(゚Д゚) ['c']+(゚Д゚) ['o']+(゚ω゚ノ +'_')[゚Θ゚]+ ((゚ω゚ノ==3) +'_') [゚ー゚] + ((゚Д゚) +'_') [(゚ー゚)+(゚ー゚)]+ ((゚ー゚==3) +'_') [゚Θ゚]+((゚ー゚==3) +'_') [(゚ー゚) - (゚Θ゚)]+(゚Д゚) ['c']+((゚Д゚)+'_') [(゚ー゚)+(゚ー゚)]+ (゚Д゚) ['o']+((゚ー゚==3) +'_') [゚Θ゚];(゚Д゚) ['_'] =(o^_^o) [゚o゚] [゚o゚];(゚ε゚)=((゚ー゚==3) +'_') [゚Θ゚]+ (゚Д゚) .゚Д゚ノ+((゚Д゚)+'_') [(゚ー゚) + (゚ー゚)]+((゚ー゚==3) +'_') [o^_^o -゚Θ゚]+((゚ー゚==3) +'_') [゚Θ゚]+ (゚ω゚ノ +'_') [゚Θ゚]; (゚ー゚)+=(゚Θ゚); (゚Д゚)[゚ε゚]='\\'; (゚Д゚).゚Θ゚ノ=(゚Д゚+ ゚ー゚)[o^_^o -(゚Θ゚)];(o゚ー゚o)=(゚ω゚ノ +'_')[c^_^o];(゚Д゚) [゚o゚]='\"';(゚Д゚) ['_'] ( (゚Д゚) ['_'] (゚ε゚+(゚Д゚)[゚o゚]+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ ((o^_^o) +(o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ ((゚ー゚) + (o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (o^_^o))+ (o^_^o)+ (゚Д゚)[゚ε゚]+((o^_^o) +(o^_^o))+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+((o^_^o) +(o^_^o))+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+((o^_^o) +(o^_^o))+ (o^_^o)+ (゚Д゚)[゚ε゚]+((o^_^o) +(o^_^o))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+((o^_^o) +(o^_^o))+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+((o^_^o) +(o^_^o))+ ((o^_^o) +(o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (o^_^o))+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚o゚]) (゚Θ゚)) ('_');

# JSfuck

[]()!+ 6 种字符构成

# jother

!+()[]{} 8 种字符构成

# brainfuck

><+-.,[] 8 种字符构成

# Ook!

由大量的 Ook.Ook!Ook? 字符串构成

Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook? Ook. Ook? Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook! Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook? Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook! Ook! Ook! Ook!
Ook! Ook! Ook? Ook. Ook? Ook! Ook. Ook? Ook! Ook! Ook! Ook! Ook! Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook? Ook. 

# short Ook!

省略了 Ook! 编码中 Ook 部分的内容,只由 .!? 3 种字符构成

..... ..... ..... ..... !?!!. ?.... ..... ..... ..... .?.?! .?... .!...
..... ..... !.?.. ..... !?!!. ?!!!! !!?.? !.?!! !!!.. ..... ..... .!.?. 

# unicode

&# 开头,可用来表示汉字、字母、数字

&#20320;&#22909;&#65292;&#36825;&#26159;&#102;&#108;&#97;&#103; → 你好,这是flag

也可由 U+%u\u 开头

\u0066\u006C\u0061\u0067
%u0066%u006C%u0061%u0067
U+0066U+006CU+0061U+0067

# URL

只编码特殊符号,通常在链接中出现,也可用于 base64

ZmxhZw%3D%3D → ZmxhZw== → flag

# 与佛论禅

佛曰作为开头,有时候出题会省略,解码需要补上

佛曰:即盡奢輸殿無呐曳盧哆寫呐死侄迦冥伊夷冥蘇奢隸怯婆無

也有新佛曰

新佛曰:梵諸梵隸梵僧梵降梵吽梵諸陀梵摩隸梵僧梵缽梵薩願耨梵咤陀願兜咤如梵

甚至熊曰

熊曰:呋食食溫家和嘶啽更

# 社会主义价值观编码

顾名思义,由 12 个核心价值观组成(富强、民主、文明、和谐;自由、平等、公正、法治;爱国、敬业、诚信、友善

公正公正公正诚信文明公正民主公正法治

# 常见乱码

常见乱码

例题:[Hackergame2020] 从零开始的火星文

# 加密

# Morse 码

image-20210122143624903

# 敲击码

  1  2  3  4  5
1 A  B C/K D  E
2 F  G  H  I  J 
3 L  M  N  O  P
4 Q  R  S  T  U
5 V  W  X  Y  Z

用位置表示字母,再转换成敲击码的形式,例如

image-20210125180015231

# 培根加密

image-20210122160745711

也可将 ab 替换为不同的两种字符或套用两种字体,如

To encode a message each letter of the plaintext is replaced by a group of five of the letters ‘A’ or ‘B’.

# 跳舞的小人

第一种

img

第二种

跳舞的小人

# 圣堂武士

img

# 银河字母

银河字母

# 猪圈密码

猪圈密码还有很多种变形,在此就举例一种最常见的

豬圈密碼- 维基百科,自由的百科全书

# 栅栏加密

# 普通型

把密文分成 n 组,每组 m 个,也称为 m 栏,把每组的第一个、第二个…… 字母连在一起,即构成密文

解密时把密文分成 m 组,把每组第一个、第二个…… 字母依次连起来,就得到了明文

解密如不知道栏数可以爆破

# W 型

W . . . E . . . C . . . R . . . L . . . T . . . E
. E . R . D . S . O . E . E . F . E . A . O . C .
. . A . . . I . . . V . . . D . . . E . . . N . .

行数 = 栏数

加密时明文从上至下写,到达最底部时再回头向上,一直到把想要加密的内容写完,再依次把每行连起来,即得到密文

# 凯撒加密

凯撒的变形很多,但万变不离其宗,都是在明文的基础上进行偏移,最基础的版本就是基于英文字母表的偏移

image-20210122170515448

解密时不知道密钥也可以爆破

# 维吉尼亚加密

维吉尼亚密码

设第一行为明文字母,第一列为密钥字母,交叉点即为密文字母

明文 cat ,密钥 dog ,密文 foz

例题:[MoeCTF2020] 大帝的征程 #维吉尼亚 Ex

Xjii xgondo pwheoet wa gjmiulkx cnt bmstr. Lr klvg adl roido. Uy gvgacmy wi boqfwif. Adl dh sqbmrhh kt tmnhgnvqi. Lx fruihss hq tzqxgd, qvy kpr egswif. Ij lmsplbp ti wjebbzf.

Wze, fmh stiwqioa hkfjujccu. I hwakfas bicri wgr, pma uy vxklb ejfzr. Ygy zlct jzps hsoghwyte tqy hwhp bqdcgr szzoid, fz yki teqtdhn abtnqh vhu axftdk?

Uy zstkul, rwig x ynopkod wovtqp, ft vgwlfb v hgtb itupf id i acac lr yki ooukot{ig3_oqf1_Ymiedmms_BzVn3_s0w_w0_3csO}, iir rqbmyhh c byo xftzqgwh jqr xqhgtkc, us wlg wezgr.

无密钥可爆破:https://guballa.de/vigenere-solver

This player dreamed of sunlight and trees. Of fire and water. It dreamed it created. And it dreamed it destroyed. It dreamed it hunted, and was hunted. It dreamed of shelter.

Hah, the original interface. A million years old, and it still works. But what true structure did this player create, in the reality behind the screen?

It worked, with a million others, to sculpt a true world in a fold of the moectf{th3_rea1_Vigenere_MaYb3_n0t_s0_3asY}, and created a big creature for himself, in the world.

# 自动密钥加密

也被称为 autokey,与维吉尼亚类似,区别在于密钥的开头为一个关键词,之后则是明文的重复

通常出题并不会给密钥,可以直接爆破出明文

http://www.practicalcryptography.com/cryptanalysis/stochastic-searching/cryptanalysis-autokey-cipher/

# 键盘密码

键盘密码也有很多种,在此举较为常见的两种例子

第一种, QWE 对应 ABC

computer-qwe

第二种,将对应字母连起来,对应形状即为明文,例如 QAZSEDCFT 对应 WZXCVBSEFT YGVBN VGYHNGH IUYGVBNMKJ 对应 FLAG (不固定)

# emoji 加密

较为常见的有两种

第一种,codemoji,如果是链接的话,可以还原短链接后解 url,再解 base64,可以直接看到密钥(参考),或者直接工具爆破(codemoji cracker

第二种,emoji-aes,一定需要密钥,暂时没有什么爆破的方法

请我喝[茶]~( ̄▽ ̄)~*

De3B4to 微信支付

微信支付

De3B4to 支付宝

支付宝