【Python】利用 Emoji 隐藏数据
背景介绍
Emoji 突变「U盘」?表情符号被曝能“走私”数据,程序员亲测:真的可以!-CSDN博客
Unicode使用代码点(Code Point)序列表示文本,每个代码点对应一个特定字符。通常表示为U+XXXXXXXX
格式的十六进制数。对于拉丁字母等简单字符,代码点与显示字符一一对应。但在复杂书写系统中,单个显示字符可能由多个代码点组合而成。
Unicode定义了256个特殊代码点作为变体选择器(Variation Selectors,VS),范围从VS-1(U+FE00)到VS-256(U+E01EF)。它们不直接显示,而是修改前一个字符的样式。例如,g
(U+0067)后跟VS-2(U+FE01)仍显示为g
,但复制时会包含隐藏的选择器。
由于256个选择器正好对应一个字节(0-255),这提供了一种在Unicode文本中隐藏任意字节数据的方法。
编码实现
字节转变体选择器
def byte_to_variation_selector(byte: int) -> str:
if byte < 16:
return chr(0xFE00 + byte)
else:
return chr(0xE0100 + (byte - 16))
该函数将字节值映射到对应的变体选择器字符。前16个字节(0-15)对应U+FE00
至U+FE0F
,后续字节(16-255)对应U+E0100
至U+E01EF
。
数据编码
def encode(base: str, data: bytes) -> str:
encoded = [base]
for byte in data:
encoded.append(byte_to_variation_selector(byte))
return ''.join(encoded)
将基础字符(如😊
)与所有字节转换后的变体选择器拼接,生成最终字符串。例如:
data = b'hello' # 字节值 [0x68, 0x65, 0x6c, 0x6c, 0x6f]
encoded = encode('😊', data)
print(encoded) # 输出:😊󠅘󠅕󠅜󠅜󠅟
print(repr(encoded)) # 输出:'😊\U000e0158\U000e0155\U000e015c\U000e015c\U000e015f'
解码实现
变体选择器转字节
def variation_selector_to_byte(c: str) -> int | None:
cp = ord(c)
if 0xFE00 <= cp <= 0xFE0F:
return cp - 0xFE00
elif 0xE0100 <= cp <= 0xE01EF:
return (cp - 0xE0100) + 16
else:
return None
通过判断字符的Unicode码点,反向解析出原始字节值。
数据解码
def decode(encoded: str) -> bytes:
result = []
for c in encoded:
byte = variation_selector_to_byte(c)
if byte is not None:
result.append(byte)
elif result: # 遇到非变体选择符且已开始解码,终止
break
return bytes(result)
遍历字符串,跳过基础字符后的非变体选择符,遇到首个变体选择符开始解析,直至再次遇到无效字符停止。
decoded = decode(encoded)
print(decoded.decode('utf-8')) # 输出:hello
面向对象封装
class UnicodeDataHider:
def __init__(self, base: str):
"""
初始化时设置基础字符,后续将基于该字符进行编码。
:param base: 基础字符,例如 '😊'
"""
self.base = base
@staticmethod
def char_to_variation_selector(char: str) -> str:
"""
将字符转换为对应的Unicode变体选择器字符。
:param char: 需要转换的字符
:return: 对应的Unicode变体选择器字符
"""
byte = ord(char) # 获取字符的Unicode码点(字节值)
if byte < 16:
return chr(0xFE00 + byte)
else:
return chr(0xE0100 + (byte - 16))
def encode(self, data: str) -> str:
"""
将字符串编码为隐藏在基础字符后的Unicode变体选择器字符串。
:param data: 需要编码的数据(普通字符串)
:return: 编码后的字符串
"""
encoded = [self.base]
for char in data:
encoded.append(self.char_to_variation_selector(char))
return ''.join(encoded)
@staticmethod
def variation_selector_to_char(c: str) -> str | None:
"""
将Unicode变体选择器字符反向转换为原始字符。
:param c: 变体选择器字符
:return: 对应的原始字符,或None(无效字符)
"""
cp = ord(c)
if 0xFE00 <= cp <= 0xFE0F:
return chr(cp - 0xFE00)
elif 0xE0100 <= cp <= 0xE01EF:
return chr((cp - 0xE0100) + 16)
else:
return None
def decode(self, encoded: str) -> str:
"""
解码隐藏在Unicode变体选择器中的字符串数据。
:param encoded: 编码后的字符串
:return: 解码后的原始字符串
"""
result = []
for c in encoded:
char = self.variation_selector_to_char(c)
if char is not None:
result.append(char)
elif result: # 遇到非变体选择符且已开始解码,终止
break
return ''.join(result)
# 使用示例
if __name__ == '__main__':
base_char = '😊'
data = 'hello' # 普通字符串
# 创建UnicodeDataHider实例
hider = UnicodeDataHider(base_char)
# 编码数据
encoded = hider.encode(data)
print(f'Encoded: {repr(encoded)}')
# 解码数据
decoded = hider.decode(encoded)
print(f'Decoded: {decoded}')
应用场景与注意事项
- 隐蔽传输:可在看似正常的文本中隐藏元数据或水印。
- 兼容性:需确保处理程序保留变体选择器,部分环境可能丢弃不可识别字符。
- 显示差异:某些渲染器可能忽略变体选择器,导致隐藏数据丢失。
通过巧妙利用Unicode特性,我们实现了数据的隐蔽存储与传输。