数据安全:基于不可见字符的文本水印技术

一、概述

信息媒体的数字化为信息的存取提供了极大的方便,越来越多的业务现在都是基于网络信息完成的。与此同时,信息的泄露,篡改,盗版等也困扰这很多公司以及个人。那么如何降低这些风险或者说泄露了信息如何溯源呢?数字水印技术则在这方面提供了一系列追溯的功能,可以追溯信息在那个环节泄露。

数字水印技术由很多,基于多媒体图片,音频以及视频等技术研究比较深入,受限制于文本的特性,单独对文本的数字水印技术研究比较少,一般需要结合特定的文本格式进行解析。

如下是比较常见的文本水印技术。

(1)基于普通文本文件格式信息的技术,包括利用字符或者单词字移的技术、利用文本行距的行移技术、利用字符特征(字体、颜色、高度、宽度、笔画宽度、是否有下划线、是否为斜体等、字符拓扑结构)的技术等;

(2)基于不可见编码的技术,包括替换法、追加法、基于冗余编码的技术、字符的图形与编码相互独立的技术等; 基于文本内容的技术,包括同义词替换技术、基于句法的文本数字水印技术、基于语义的文本数字水印技术等;

(3)基于汉字结构的技术,包括利用偏旁部首的可组合的特性、字符内偏旁部首之间的距离的技术;

(4)基于图像水印技术的技术,包括基于各种空域、变换域的水印嵌入技术;

(5)基于特殊格式文件的技术,例如基于HTML、PDF等特殊文件格式的水印嵌入技术。

二、不可见编码的技术

那么如何单纯的只对文本进行水印技术编码呢?

上面第二点,可以基于”不可见编码的技术”对文本进行水印添加。

Unicode 中有一类格式字符,不可见,不可打印,主要作用于调整字符的显示格式,所以我们将其称为零宽字符。

零宽字符主要有以下几类:

零宽度空格符 (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 : 用于在混合文字方向的多种语言文本中,规定排版文字书写方向为右至左

我们可以使用零宽字符的特性对文本进行水印加密。

三、实现

1、代码实现

package com.test.watermark;

/**
 * @author 小白
 * @version 1.0
 * 类说明
 * @date 2020/12/2 9:15
 */
public class WatermarkUtils {
    private static int WATERMARK_POS_NONE = 0;
    private static int WATERMARK_POS_HEAD = 1;
    private static int WATERMARK_POS_TAIL = 2;
    /**
     * 将字符串转换成二进制字符串,以空格相隔
     *
     * @param input
     * @return
     */
    private String strToBinary(String input) {
        char[] strChar = input.toCharArray();
        String result = "";
        String tmp = "";
        for (int i = 0; i < strChar.length; i++) {
            tmp = Integer.toBinaryString(strChar[i]);

            while(tmp.length() < 16) {
                tmp = "0" + tmp;
            }
            result += tmp;
            result += " ";
        }
        return result.trim();
    }

    /**
     * 将二进制字符串转换成int数组
     *
     * @param binStr
     * @return
     */
    public int[] binstrToIntArray(String binStr) {
        char[] temp=binStr.toCharArray();
        int[] result=new int[temp.length];
        for(int i=0;i<temp.length;i++) {
            result[i]=temp[i]-48;
        }
        return result;
    }

    /**
     * 将二进制转换成字符
     *
     * @param binStr
     * @return
     */
    public char binstrToChar(String binStr){
        int[] temp=binstrToIntArray(binStr);
        int sum=0;
        for(int i=0; i<temp.length;i++){
            sum +=temp[temp.length-1-i]<<i;
        }
        return (char)sum;
    }

    /**
     * 将以空格相隔的二进制转换为字符串
     *
     * @param input
     * @return
     */
    public String binaryToStr(String input){
        String[] tempStr=input.split(" ");
        char[] tempChar=new char[tempStr.length];
        for(int i=0;i<tempStr.length;i++) {
            tempChar[i]=binstrToChar(tempStr[i]);
        }
        return String.valueOf(tempChar);
    }


    /**
     * 二进制字符串转换为零宽字符
     *
     * @param input
     * @return
     */
    private String binaryToZeroWidth(String input) {
        String[] stringArray = input.split(" ");
        String result = "";
        for(int i = 0; i < stringArray.length; i++) {
            for(int j = 0; j < stringArray[i].length(); j++) {
                //数字转换
                int num = Integer.parseInt(stringArray[i].charAt(j) + "");
                if(num == 1) {
                    result += '\u200b'; // \u200b 零宽度字符(zero-width space)
                } else if(num == 0) {
                    result += '\u200c'; // \u200c 零宽度断字符(zero-width non-joiner)
                } else {
                    result += '\u200d'; // \u200d 零宽度连字符 (zero-width joiner)
                }
                result += '\ufeff'; // \ufeff 零宽度非断空格符 (zero width no-break space)
            }
        }
        return result;
    }

    /**
     * 零宽字符转二进制
     *
     * @param input
     * @return
     */
    private String zeroWidthToBinary(String input) {
        String result = "";
        String[] binaryStr = input.split("\ufeff");
        for(int i = 0; i < binaryStr.length; i++) {
            if(binaryStr[i].equals("\u200B")) {
                result += "1";
            } else if(binaryStr[i].equals("\u200C")) {
                result += "0";
            }
            if((i + 1) % 16 == 0) {
                result += " ";
            }
        }
        return result;
    }


    /**
     * 字符串编码
     *
     * @param input
     * @return
     */
    private String encode(String input) {
        String binary = strToBinary(input);
        String result = binaryToZeroWidth(binary);
        return result;
    }

    /**
     * 零宽字符解码
     *
     * @param input
     * @return
     */
    private String decode(String input) {
        String binary = zeroWidthToBinary(input);
        String result = binaryToStr(binary);
        return  result;
    }

    /**
     * 添加水印数据
     *
     * @param src 源文件字符串
     * @param watermark 水印
     * @return 添加水印后的字符串
     */
    private String addWatermark(String src, String watermark, int pos) {
        if(pos == WATERMARK_POS_HEAD) {
            return watermark + src;
        } else if(pos == WATERMARK_POS_TAIL) {
            return src + watermark;
        }
        return src;
    }


    /**
     * 提取水印数据
     *
     * @param input 添加水印的文本
     * @return
     */
    private String extractWatermark(String input, int pos) {
        String watermark = "";
        if(pos == WATERMARK_POS_HEAD) {
           for(int i = 0; i < input.length(); i++) {
                if(input.charAt(i) != '\u200b' && input.charAt(i) != '\u200c' && input.charAt(i) != '\u200d' && input.charAt(i) != '\ufeff') {
                    watermark = input.substring(0, i);
                    break;
                }
           }
        } else if(pos == WATERMARK_POS_TAIL) {
            for(int i = input.length() - 1; i >= 0; i--) {
                if(input.charAt(i) != '\u200b' && input.charAt(i) != '\u200c' && input.charAt(i) != '\u200d' && input.charAt(i) != '\ufeff') {
                    watermark = input.substring(i + 1);
                    break;
                }
            }
        }
        return watermark;
    }
}

2、main函数

 public static void main(String[] args) throws Exception {
		WatermarkUtils watermarkUtils = new WatermarkUtils();

        String input = "测试下水印添加";
        System.out.println("原文本:\"" + input + "\",文本长度:" + input.length());
        String watermarkSrc = "我是小白,我的工号为123456";
        System.out.println("水印文本:\"" + watermarkSrc + "\",文本长度:" + watermarkSrc.length());
        String encode = watermarkUtils.encode(watermarkSrc);
        System.out.println("水印编码:\"" + encode + "\",编码长度:" + encode.length());

        System.out.println("=================================");
        System.out.println("文本前添加水印");
        String result = watermarkUtils.addWatermark(input, encode, WATERMARK_POS_HEAD);
        System.out.println("输出:\"" + result + "\",文本长度:" + result.length());
        result = watermarkUtils.extractWatermark(result, WATERMARK_POS_HEAD);
        String watermark = watermarkUtils.decode(result);
        System.out.println("提取水印并解码:" + watermark);

        System.out.println("=================================");
        System.out.println("文本后添加水印");
        result = watermarkUtils.addWatermark(input, encode, WATERMARK_POS_TAIL);
        System.out.println("输出:\"" + result + "\",文本长度:" + result.length());
        result = watermarkUtils.extractWatermark(result, WATERMARK_POS_TAIL);
        watermark = watermarkUtils.decode(result);
        System.out.println("提取水印并解码:" + watermark);
 }

执行后输出,单纯从文本上看是不是没有变化呢,不过长度有变化,水印已经嵌入文本中了。

原文本:"测试下水印添加",文本长度:7
水印文本:"我是小白,我的工号为123456",文本长度:16
水印编码:"‌​​‌‌‌​‌‌‌‌​‌‌‌​‌​​‌‌​​‌‌‌​‌​​​​‌​‌​​​‌‌‌‌‌‌​​​​‌​​​‌​​‌‌​​​​​‌​​​​​​​​​‌‌‌‌​​‌‌‌​​‌‌‌​‌‌‌‌​‌‌‌​‌​​​‌​​‌​‌‌‌‌​‌‌‌​‌​​​‌​​​​‌‌​‌​‌​‌​‌‌​​​​​​‌​​​‌​‌‌​​​‌‌‌​​​‌​‌‌‌‌‌‌‌‌‌‌‌​​‌‌‌​‌‌‌‌‌‌‌‌‌‌​​‌‌​‌‌‌‌‌‌‌‌‌‌‌​​‌‌​​‌‌‌‌‌‌‌‌‌‌​​‌​‌‌‌‌‌‌‌‌‌‌‌‌​​‌​‌​‌‌‌‌‌‌‌‌‌‌​​‌​​‌",编码长度:512
=================================
文本前添加水印
输出:"‌​​‌‌‌​‌‌‌‌​‌‌‌​‌​​‌‌​​‌‌‌​‌​​​​‌​‌​​​‌‌‌‌‌‌​​​​‌​​​‌​​‌‌​​​​​‌​​​​​​​​​‌‌‌‌​​‌‌‌​​‌‌‌​‌‌‌‌​‌‌‌​‌​​​‌​​‌​‌‌‌‌​‌‌‌​‌​​​‌​​​​‌‌​‌​‌​‌​‌‌​​​​​​‌​​​‌​‌‌​​​‌‌‌​​​‌​‌‌‌‌‌‌‌‌‌‌‌​​‌‌‌​‌‌‌‌‌‌‌‌‌‌​​‌‌​‌‌‌‌‌‌‌‌‌‌‌​​‌‌​​‌‌‌‌‌‌‌‌‌‌​​‌​‌‌‌‌‌‌‌‌‌‌‌‌​​‌​‌​‌‌‌‌‌‌‌‌‌‌​​‌​​‌测试下水印添加",文本长度:519
提取水印并解码:我是小白,我的工号为123456
=================================
文本后添加水印
输出:"测试下水印添加‌​​‌‌‌​‌‌‌‌​‌‌‌​‌​​‌‌​​‌‌‌​‌​​​​‌​‌​​​‌‌‌‌‌‌​​​​‌​​​‌​​‌‌​​​​​‌​​​​​​​​​‌‌‌‌​​‌‌‌​​‌‌‌​‌‌‌‌​‌‌‌​‌​​​‌​​‌​‌‌‌‌​‌‌‌​‌​​​‌​​​​‌‌​‌​‌​‌​‌‌​​​​​​‌​​​‌​‌‌​​​‌‌‌​​​‌​‌‌‌‌‌‌‌‌‌‌‌​​‌‌‌​‌‌‌‌‌‌‌‌‌‌​​‌‌​‌‌‌‌‌‌‌‌‌‌‌​​‌‌​​‌‌‌‌‌‌‌‌‌‌​​‌​‌‌‌‌‌‌‌‌‌‌‌‌​​‌​‌​‌‌‌‌‌‌‌‌‌‌​​‌​​‌",文本长度:519
提取水印并解码:我是小白,我的工号为123456

我们把输出的文本复制到bejson中,可以看到水印的占位哦。
在这里插入图片描述

  • 4
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值