零宽字符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>; }

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

相关推荐
ba_pi27 分钟前
每天写点什么2026-02-04(2.1)信息安全
安全·web安全
菩提小狗1 小时前
小迪安全2023-2024|第5天:基础入门-反弹SHELL&不回显带外&正反向连接&防火墙出入站&文件下载_笔记|web安全|渗透测试|
笔记·安全·web安全
迎仔8 小时前
11-云网络与混合云运维:弹性数字世界的交通管理
网络·安全·web安全
pitch_dark8 小时前
渗透测试系统基础篇——kali系统
网络·安全·web安全
独行soc8 小时前
2026年渗透测试面试题总结-20(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
黑客老李19 小时前
web渗透实战 | js.map文件泄露导致的通杀漏洞
安全·web安全·小程序·黑客入门·渗透测试实战
liann1191 天前
3.1_网络——基础
网络·安全·web安全·http·网络安全
独行soc1 天前
2026年渗透测试面试题总结-17(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
独行soc1 天前
2026年渗透测试面试题总结-18(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
旺仔Sec1 天前
一文带你看懂免费开源 WAF 天花板!雷池 (SafeLine) 部署与实战全解析
web安全·网络安全·开源·waf