吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2938|回复: 5
收起左侧

[Python 原创] 使用零宽字符对文本加密的实现

[复制链接]
TLHorse 发表于 2021-1-22 09:41

本文为 www.52pojie.cn 首发
《使用零宽字符对文本加密的实现》
@TLHorse

0x0 前言

说来话长。其实就是前几天我看到了一篇介绍Unicode的文章,里面介绍Unicode字符的广泛性。其中有一类字符叫做零宽字符,它们在电脑上输入,不可见,也不可打印,甚至输入都不会占空间,作用是控制文字排列或解决个别语言中的排版问题。

常见的零宽字符有以下六种:

中文名 英文名 U+ 作用
零宽度空格符 zero-width space U+200B 用于较长单词的换行分隔。
零宽度非断空格符 zero width no-break space U+FEFF 用于阻止特定位置的换行分隔。
零宽度连字符 zero-width joiner U+200D 用于阿拉伯文与印度语系等文字中,使不会发生连字的字符间产生连字效果。
零宽度断字符 zero-width non-joiner U+200C 用于阿拉伯文、德文、印度语系等文字中,阻止会发生连字的字符间的连字效果。
左至右符 left-to-right mark U+200E 用于在混合文字方向的多种语言文本中(例:混合左至右书写的英语与右至左书写的希伯来语),规定排版文字书写方向为左至右。
右至左符 right-to-left mark U+200F 用于在混合文字方向的多种语言文本中,规定排版文字书写方向为右至左。

使用这些零宽字符可以实现发空白消息、发空白朋友圈等效果。不妨动脑筋一想,我们可以把这几种字符组合起来,就可以实现加密字符串的效果。

0x1 基本思路

这篇文章咱们只介绍一个初步实现,说不定以后我会出后续,给应用添加更多功能。下面是加解密的流程图。


ZWE加解密流程.jpg

总之我认为解密过程较为繁琐。

0x2 核心实现

激动人心的时刻到啦。让我们一步步编写。
首先在全局定义三个常量:

ZW_ONE = u"\u200b" # 用来翻译1
ZW_ZERO = u"\u200c" # 用来翻译0
ZW_SEP = u"\u200d" # 用来翻译字符之间的间隔

加密函数 str2zwstr(origin)

首先我们新建一个空数组,用来存储字符串每一项,并且遍历明文,使明文的每个字符先用ord()转换成十进制数字,再用bin()转换成二进制。再转换成字符串格式,最后去掉0b前缀

bin_text = []
for char in origin:
    bin_text.append(str(bin(ord(char))).lstrip('0b'))

再创建一个空字符串,用来存储最终的结果:

final_str = ""

之后进行两次遍历,先浅层遍历bin_text,然后为每一项深层遍历:

for item in bin_text: # 遍历大数组每一项
    for binchar in item: # 遍历每一项中的0和1
        final_str += ZW_ONE if binchar == "1" else ZW_ZERO # 把0、1分别翻译成两种零宽度字符串
    final_str += ZW_SEP # 每一项(字符)结束后,插入一个分隔符号
final_str.rstrip(ZW_SEP) # 去掉
return final_str

最后返回final_str即可。
整体代码:

def str2zwstr(origin):
    bin_text = []
    for char in origin:
        bin_text.append(str(bin(ord(char))).lstrip('0b'))

    final_str = ""
    for item in bin_text:
        for binchar in item:
            final_str += ZW_ONE if binchar == "1" else ZW_ZERO
        final_str += ZW_SEP
    final_str.rstrip(ZW_SEP)
    return final_stry

解密函数zwstr2str(enc_str)

首先我建议大家再看看基本思路中的流程图。解密不大相同。因为一开始我们要把翻译后的数据存储到第一个字符上,但是遇到分隔符后,我们又得新建一个字符,并把接下来的翻译后的数据存储到第二个字符串上,因此我们要编写一个函数apponlast(arr, sth),每次运行,都将sth拼接到arr的最后一项中,如果arr的项数为0,即新增一个元素。

我这里用的代码极为简洁:

def apponlast(arr, sth):
    la = len(arr) # la 是 arr 的长度
    if la: arr[la-1] += sth # 如果长度不为0,那么就把 arr 的最后一项与 sth 字符串拼接
    else: arr.append(sth) # 如果数组里没有元素,新建一个元素

之后编写解密函数:

def zwstr2str(enc_str):
    arr_oz = [] # 由0和1构成的字符串构成的数组
    for char in enc_str: # 在密文里遍历
        if char == ZW_ONE: apponlast(arr_oz, "1") # 如果是\200b,翻译成1
        elif char == ZW_ZERO: apponlast(arr_oz, "0") # 如果是\200c,翻译成0
        elif char == ZW_SEP: arr_oz.append("") # 如果是分隔符,把数组新建一项,重新开始循环
        else: print("Input contains non-ZW string. Aborted."); getinput() # 如果密文中有非零宽字符,终止解密并回到程序主函数(我们一会要编写)

    for idx in range(0, len(arr_oz)-1): # 遍历这个由0和1构成的字符串构成的数组
        arr_oz[idx] = chr(int(arr_oz[idx], 2)) # 把每一项先转换为int(注意二进制参数),然后用chr转换为字符

    return "".join(arr_oz) # 拼接

完善程序

接下来我们为程序添加一个主函数,并且加些花哨的功能。

if __name__ == '__main__':
    pbanner()
    getinput()

pbanner()用来打印banner:


def pbanner():
    banner = colored(f"""
      ______
     /___  /\\  Zerowidth String Encoder | @TLHorse from 52pojie
    //  / / \\\\ Type in then ENTER. The encoded string
    \\\\ / /__// will be copied & printed.
     \/_____/  Commands | ::openweb:: ::banner:: ::quit:: ::switchmode::
    """, 'yellow')
    print(banner)

需要说明一下,上面的colored函数需要依赖一个第三方库termcolor,可以打印出彩色字符串。

我们在全局设置两个变量:

MODE_ENCODE = True # 用来记录模式是加密还是解密
LAST_RESULT = "" # 用来记录上次操作的结果

getinput是一个递归,可以像命令行一样获取用户输入,代码比较复杂,功能很多,本来是有注释的,结果浏览器编辑的时候不小心给关了,没有恢复成功。大家自己摸索吧:

def getinput():
    global MODE_ENCODE, LAST_RESULT
    info = ""
    if MODE_ENCODE: info = colored("ENCODE", 'red')
    else: info = colored("DECODE", 'green')
    input_str = input(f'[{info}] ')

    if   input_str == '::openweb::':    os.system('open https://www.52pojie.cn') # 打开吾爱网页
    elif input_str == '::banner::':     pbanner() #打印banner
    elif input_str == '::quit::':       sys.exit(0) #退出程序
    elif input_str == '::switchmode::': MODE_ENCODE = False if MODE_ENCODE else True #切换加解密
    elif input_str == '::cp::':         os.system(f'echo {LAST_RESULT} | pbcopy') #复制结果

    if input_str.startswith("::") and input_str.endswith("::"): getinput() #检测是否为命令

    out = str2zwstr(input_str) if MODE_ENCODE == True else zwstr2str(input_str)
    print(colored('     >>> "', 'green')+ out + colored('"', 'green'))
    LAST_RESULT = out
    getinput()

别忘了import进类库:

import os, sys
from termcolor import colored

大功告成!

后记

首先,想说明一点,文中的结果复制功能是基于pbcopy命令的,这个只有Linux和Unix有,Windows没有。所以Windows小伙伴们记得使用pyperclip库实现复制功能。

其次,我也不是程序员,所以代码的繁琐与不妥当之处欢迎跟帖指正。

最后,这篇文章很有可能会出续集哦!
所有代码长这样:

import os, sys
from termcolor import colored

ZW_ONE = u"\u200b"
ZW_ZERO = u"\u200c"
ZW_SEP = u"\u200d"

MODE_ENCODE = True
LAST_RESULT = ""

def pbanner():
    banner = colored(f"""
      ______
     /___  /\\  Zerowidth String Encoder | @TLHorse from 52pojie
    //  / / \\\\ Type in then ENTER. The encoded string will be printed.
    \\\\ / /__// Commands | ::openweb:: ::banner:: ::quit:: ::switchmode::
     \/_____/  ::cp::
    """, 'yellow')
    print(banner)

def apponlast(arr, sth):
    la = len(arr)
    if la: arr[la-1] += sth
    else: arr.append(sth)

def str2zwstr(origin):
    bin_text = []
    for char in origin:
        bin_text.append(str(bin(ord(char))).lstrip('0b'))

    final_str = ""
    for item in bin_text:
        for binchar in item:
            final_str += ZW_ONE if binchar == "1" else ZW_ZERO
        final_str += ZW_SEP
    final_str.rstrip(ZW_SEP)
    return final_str

def zwstr2str(enc_str):
    arr_oz = []
    for char in enc_str:
        if char == ZW_ONE: apponlast(arr_oz, "1")
        elif char == ZW_ZERO: apponlast(arr_oz, "0")
        elif char == ZW_SEP: arr_oz.append("")
        else: print("Input contains non-ZW string. Aborted."); getinput()

    for idx in range(0, len(arr_oz)-1):
        arr_oz[idx] = chr(int(arr_oz[idx], 2))

    return "".join(arr_oz)

def getinput():
    global MODE_ENCODE, LAST_RESULT
    info = ""
    if MODE_ENCODE: info = colored("ENCODE", 'red')
    else: info = colored("DECODE", 'green')
    input_str = input(f'[{info}] ')

    if   input_str == '::openweb::':    os.system('open https://www.52pojie.cn')
    elif input_str == '::banner::':     pbanner()
    elif input_str == '::quit::':       sys.exit(0)
    elif input_str == '::switchmode::': MODE_ENCODE = False if MODE_ENCODE else True
    elif input_str == '::cp::':         os.system(f'echo {LAST_RESULT} | pbcopy')

    if input_str.startswith("::") and input_str.endswith("::"): getinput()

    out = str2zwstr(input_str) if MODE_ENCODE == True else zwstr2str(input_str)
    print(colored('     >>> "', 'green')+ out + colored('"', 'green'))
    LAST_RESULT = out
    getinput()

if __name__ == '__main__':
    pbanner()
    getinput()

代码下载在这里:链接:https://share.weiyun.com/PyUOPC6F 密码:zsetlh

免费评分

参与人数 4威望 +1 吾爱币 +14 热心值 +4 收起 理由
三滑稽甲苯 + 1 + 1 我很赞同!
w92vv + 1 + 1 感谢分享
苏紫方璇 + 1 + 10 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
陌路无人 + 2 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

xiahhhr 发表于 2021-1-22 09:48
有趣的代码,哈哈哈
wuai_leader 发表于 2021-1-22 10:07
感觉可以完成那种普通编辑器打开是一篇文章,这个程序打开是信息的那种加密
有点像谍战片里面的一本书是密码本那种
thinkingbullet1 发表于 2021-1-22 10:10
笙若 发表于 2021-1-22 13:15
这样加密的话体积是不是变成原来的64倍了
helian147 发表于 2021-1-24 18:15
\u200b就在vx上碰到过,下载下来保存不了,仔细看就有它
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-25 19:33

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表