零宽字符Zero-Width Characters

零宽字符在开发中的应用与注意事项

零宽字符(Zero-Width Characters)是一类不可见但实际存在的Unicode字符,它们在开发中有多种用途但也可能带来问题。

常见的零宽字符

  1. 零宽度空格 (U+200B)

    • 最常见的零宽字符
    • HTML中可用​表示
  2. 零宽度非连接符 (U+200C)

    • 用于某些语言的排版
      (例如,"A\u200CB""AB" 在程序中被视为不相等)
  3. 零宽度连接符 (U+200D)

    • 用于表情符号的组合
  4. 左至右标记 (U+200E) 和 右至左标记 (U+200F)

    • 控制文本方向
  5. 单词连接符 (U+2060)

    • 防止换行时断开单词

开发中的用途

  1. 水印与隐藏信息

    复制代码

    javascript

    // 在文本中嵌入隐藏信息 function embedHiddenText(visibleText, hiddenText) { const zeroWidthSpace = '\u200B'; const zeroWidthNonJoiner = '\u200C'; let binaryHidden = hiddenText.split('').map(c => c.charCodeAt(0).toString(2).padStart(8, '0') ).join(''); let steganographed = visibleText; binaryHidden.split('').forEach(bit => { steganographed += bit === '0' ? zeroWidthSpace : zeroWidthNonJoiner; }); return steganographed; }

  2. 防止恶意爬取

    • 在关键数据中插入零宽字符,干扰爬虫解析
  3. 文本格式标记

    • 在不影响显示的情况下标记文本段落

潜在问题与解决方案

  1. 调试困难

    复制代码

    javascript

    // 检测字符串中的零宽字符 function hasZeroWidthChars(str) { const zeroWidthRegex = /[\u200B-\u200F\u2060\uFEFF]/g; return zeroWidthRegex.test(str); } // 移除所有零宽字符 function removeZeroWidthChars(str) { return str.replace(/[\u200B-\u200F\u2060\uFEFF]/g, ''); }

  2. 数据验证问题

    复制代码

    javascript

    // 严格的用户名验证(排除零宽字符) function isValidUsername(username) { return !/[\u200B-\u200F\u2060\uFEFF]/.test(username) && /^[a-zA-Z0-9_\-]{3,20}$/.test(username); }

  3. 数据库存储问题

    • MySQL: SET NAMES utf8mb4 COLLATE utf8mb4_bin
    • PostgreSQL: TEXT COLLATE "C"

最佳实践

  1. 明确文档记录何时使用零宽字符
  2. 添加注释说明使用原因
  3. 输入过滤关键字段应过滤这类字符
  4. 输出转义在显示前处理可能有害的零宽字符

实现说明

  1. 编码过程

    • encodeToInvisible函数首先将输入字符串的每个字符转换为16位的二进制表示(确保统一长度)
    • 然后将每个二进制数字替换为对应的零宽度Unicode字符:
      • 1\u200b(零宽度空格)
      • 0\u200c(零宽度非连接符)
      • \u200d(零宽度连接符)
    • 最后在前面添加一个\ufeff作为标记
  2. 解码过程

    • decodeFromInvisible函数反向操作,将零宽度字符转换回二进制数字和空格
    • 然后将每16位二进制数转换回原始的Unicode字符
  3. 特点

    • 完全不可见的隐写术技术
    • Unicode兼容性好,可以在大多数现代系统中使用
    • JS内置支持,无需额外库

注意事项

  1. 安全性:这不是加密,只是隐写术。任何知道这个方法的人都可以解码内容。
  2. 长度限制:编码后的长度会显著增加(大约16倍)。
  3. 兼容性:某些系统可能会过滤或修改这些特殊Unicode字符。
  4. 应用场景:适用于水印、元数据隐藏等场景。
复制代码
javascript 复制代码
// 编码函数:将普通字符串转为隐形字符
function encodeToInvisible(text) {
    // 将字符串转换为二进制表示
    let binaryString = '';
    for (let i = 0; i < text.length; i++) {
        // 获取字符的Unicode码点,然后转换为16位二进制字符串
        const charCode = text.charCodeAt(i);
        binaryString += charCode.toString(2).padStart(16, '0') + ' ';
    }
    
    // 移除最后一个多余的空格
    binaryString = binaryString.trim();
    
    // 替换二进制数字为不可见字符
    let invisibleString = '';
    for (const char of binaryString) {
        switch (char) {
            case '1':
                invisibleString += '\u200b'; // Zero Width Space
                break;
            case '0':
                invisibleString += '\u200c'; // Zero Width Non-Joiner
                break;
            case ' ':
                invisibleString += '\u200d'; // Zero Width Joiner
                break;
        }
    }
    
    // 使用零宽度非断空格符作为分隔符(虽然这里我们没有多个部分要分隔)
    return '\ufeff' + invisibleString;
}

// 解码函数:将隐形字符转回普通字符串
function decodeFromInvisible(invisibleText) {
    // 移除开头的\ufeff分隔符(如果有)
    if (invisibleText.startsWith('\ufeff')) {
        invisibleText = invisibleText.substring(1);
    }
    
    // 将不可见字符转换回二进制数字和空格
    let binaryString = '';
    for (const char of invisibleText) {
        switch (char) {
            case '\u200b':
                binaryString += '1';
                break;
            case '\u200c':
                binaryString += '0';
                break;
            case '\u200d':
                binaryString += ' ';
                break;
        }
    }
    
    // 分割二进制字符串为各个字符的二进制表示
	const binaryParts = binaryString.split(' ');
	
	// 将每个16位二进制转换回Unicode字符
	let originalText = '';
	for (const part of binaryParts) {
		if (part.length === 16) { // 确保是16位二进制数
			const charCode = parseInt(part, 2);
			originalText += String.fromCharCode(charCode);
		}
	}
	
	return originalText;
}

// 使用示例
const originalText = "Hello, World!";
console.log("原始文本:", originalText);

// 编码为隐形字符
const encodedText = encodeToInvisible(originalText);
console.log("编码后的隐形文本:", encodedText);
console.log("编码后长度:", encodedText.length);
console.log("看起来是空的:", encodedText === "" ? "是" : "否");

// 解码回原始文本
const decodedText = decodeFromInvisible(encodedText);
console.log("解码后的文本:", decodedText);

// 验证是否一致
console.log("是否匹配原始文本:", decodedText === originalText ? "是" : "否");

// Vue/React组件示例:安全显示可能包含零宽字符的文本 function SafeText({ children }) { const cleanText = children.replace(/[\u200B-\u200F\u2060\uFEFF]/g, ''); return <span>{cleanText}</span>; }

在实际开发中,合理利用零宽字符可以解决特定问题,但也要注意它们可能带来的副作用。

相关推荐
周某人姓周6 小时前
sql报错注入常见7个函数
sql·安全·web安全·网络安全
是逍遥子没错6 小时前
OA渗透测试的思维盲区:从漏洞猎人到系统拆解师
web安全·网络安全·黑客·渗透测试·系统安全·oa系统·src挖掘
是逍遥子没错7 小时前
关于国内通用OA的渗透测试思路-仅供测试切勿违法使用
安全·web安全·网络安全·渗透测试·系统安全·漏洞挖掘
学习中的DGR17 小时前
[GXYCTF2019]Ping Ping Ping 1和[SUCTF 2019]EasySQL 1新手解题过程
sql·安全·web安全·网络安全·php
emma羊羊17 小时前
【wordpress-wpdiscuz-rce】
网络·web安全·wordpress
Sombra_Olivia18 小时前
Ubuntu22.04 安装Docker Vulhub遇到的问题
web安全·docker·vulhub
运筹vivo@20 小时前
攻防世界: mfw
前端·web安全·php
2301_780789661 天前
2025年UDP洪水攻击防护实战全解析:从T级流量清洗到AI智能防御
服务器·网络·人工智能·网络协议·安全·web安全·udp
hzb666661 天前
xd_day47文件上传-day55xss
javascript·学习·安全·web安全·php