Схема реализации водяного знака во внешнем интерфейсе

внешний интерфейс
Схема реализации водяного знака во внешнем интерфейсе

1. Предыстория проблемы

Чтобы предотвратить утечку информации или нарушение прав интеллектуальной собственности, в мире Интернета необходимо добавлять водяные знаки на страницы и изображения.Добавление водяных знаков можно разделить на две категории в зависимости от среды.внешний интерфейсдополнения к среде браузера изадняя частьДобавьте сервисную среду и кратко сравните характеристики этих двух методов:

Водяные знаки в интерфейсе браузера:

  • Уменьшите нагрузку на сервер и быстро реагируйте
  • Запас прочности низкий, те, у кого есть определенные знания во фронтенде, могут пропустить водяной знак и получить исходный файл с помощью различных дерзких операций.
  • Применимый сценарий: Ресурс не привязан к одному пользователю, а является ресурсом, который могут просматривать несколько пользователей.Необходимо добавлять пользовательский водяной знак, когда каждый пользователь просматривает его, что в основном используется для некоторых конфиденциальных документов или отображение конфиденциальной информации Целью страницы и водяного знака является привлечение ответственного лица к ответственности при выходе документа.

Водяные знаки на бэкэнд-сервере:

  • При обнаружении большого файла с плотными водяными знаками или сложными водяными знаками он занимает память сервера и вычисления, а время запроса слишком велико.
  • Высокий уровень безопасности, невозможность получить исходный файл до нанесения водяного знака
  • Применимые сценарии: Ресурс уникален для определенного пользователя. Исходный ресурс нужно обработать только один раз, и его не нужно обрабатывать снова после его сохранения. Цель водяного знака — указать владельца ресурса.

Здесь мы обсуждаем дополнения к интерфейсной среде браузера.

2. Анализ доходов

Кратко расскажите о текущих основных методах создания водяных знаков, которые могут использоваться другими учащимися в качестве справочных материалов в будущем.

3. План реализации

1. Реализация повторяющегося покрытия элементов dom

Начиная с эффекта, эффект, который должен быть достигнут, состоит в том, чтобы «заполнить страницу малопрозрачной и повторяющейся идентификационной информацией». Первое решение, которое пришло на ум, состояло в том, чтобы покрыть поле position:fixed div на странице и прозрачность поля параметр был низким. , установите указатель-события: none; стиль для достижения проникновения щелчка, сгенерируйте небольшой div водяного знака через цикл js в этом поле и отобразите содержимое водяного знака, которое будет отображаться в каждом div водяного знака, что просто реализовано.

<!DOCTYPE html> 
<html> 
    <head> 
        <meta charset="utf-8"> 
        <title></title> 
        <style> 
            #watermark-box { 
                position: fixed; 
                top: 0; 
                bottom: 0; 
                left: 0; 
                right: 0; 
                font-size: 24px; 
                font-weight: 700; 
                display: flex; 
                flex-wrap: wrap; 
                overflow: hidden; 
                user-select: none; 
                pointer-events: none; 
                opacity: 0.1; 
                z-index: 999;
            } 
            .watermark { 
                text-align: center; 
            } 
        </style> 
    </head> 
    <body> 
        <div> 
            <h2> 机密内容- 机密内容- 机密内容- 机密内容- 机密内容- 机密内容- </h2> 
            <br /> 
            <h2> 机密内容- 机密内容- 机密内容- 机密内容- 机密内容- 机密内容- </h2> 
            <br /> 
            <h2 onclick="alert(1)"> 机密内容- 机密内容- 机密内容- 机密内容- 机密内容- 机密内容- 机密内容- </h2> 
            <br /> 
        </div> 
        <div id="watermark-box"> 
        </div> 
        <script> 
            function doWaterMark(width, height, content) { 
                let box = document.getElementById("watermark-box"); 
                let boxWidth = box.clientWidth, 
                    boxHeight = box.clientHeight; 
                for (let i = 0; i < Math.floor(boxHeight / height); i++) { 
                    for (let j = 0; j < Math.floor(boxWidth / width); j++) { 
                        let next = document.createElement("div") 
                        next.setAttribute("class", "watermark") 
                        next.style.width = width + 'px' 
                        next.style.height = height + 'px' 
                        next.innerText = content 
                        box.appendChild(next) 
                    } 
                } 
            } 
            window.onload = doWaterMark(300, 100, '水印123') 
        </script> 
    </body> 
</html>

Эффект страницы есть, но это решение требует создания нескольких элементов DOM в цикле в js, что не является ни элегантным, ни производительным, поэтому подумайте, можно ли не генерировать столько элементов.

2. Выходное фоновое изображение холста

Первый шаг — покрыть блок с фиксированным положением на странице, затем создать холст холста, нарисовать область водяного знака, вывести водяной знак в виде изображения с помощью метода toDataURL, установить изображение в качестве фонового изображения поля и использовать фон - повторить: повторить стиль для достижения эффекта заполнения всего экрана, простой код.

 <!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <div id="info" onclick="alert(1)" >
            123
        </div>
        <script>
           (function () {
              function __canvasWM({
                container = document.body,
                width = '300px',
                height = '200px',
                textAlign = 'center',
                textBaseline = 'middle',
                font = "20px Microsoft Yahei",
                fillStyle = 'rgba(184, 184, 184, 0.6)',
                content = '水印',
                rotate = '45',
                zIndex = 10000
              } = {}) {
                const args = arguments[0];
                const canvas = document.createElement('canvas');
        
                canvas.setAttribute('width', width);
                canvas.setAttribute('height', height);
                const ctx = canvas.getContext("2d");
        
                ctx.textAlign = textAlign;
                ctx.textBaseline = textBaseline;
                ctx.font = font;
                ctx.fillStyle = fillStyle;
                ctx.rotate(Math.PI / 180 * rotate);
                ctx.fillText(content, parseFloat(width) / 2, parseFloat(height) / 2);
        
                const base64Url = canvas.toDataURL();
                const __wm = document.querySelector('.__wm');
        
                const watermarkDiv = __wm || document.createElement("div");
                const styleStr = `
                  position:fixed;
                  top:0;
                  left:0;
                  bottom:0;
                  right:0;
                  width:100%;
                  height:100%;
                  z-index:${zIndex};
                  pointer-events:none;
                  background-repeat:repeat;
                  background-image:url('${base64Url}')`;
        
                watermarkDiv.setAttribute('style', styleStr);
                watermarkDiv.classList.add('__wm');
        
                if (!__wm) {
                  container.insertBefore(watermarkDiv, container.firstChild);
                }
                
                if (typeof module != 'undefined' && module.exports) {  //CMD
                    module.exports = __canvasWM;
                } else if (typeof define == 'function' && define.amd) { // AMD
                    define(function () {
                      return __canvasWM;
                    });
                } else {
                    window.__canvasWM = __canvasWM;
                }
              })();
                
            // 调用
            __canvasWM({
              content: '水印123'
            });
        </script>
    </body>
</html>

3. Фоновое изображение реализации SVG

Он аналогичен методу генерации фоновых изображений canvas, за исключением того, что метод генерации фоновых изображений заменен генерацией svg, а совместимость canvas немного лучше, чем у svg. Сравнение совместимости:

canvas

image.png

svg

image.png

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <div id="info" onclick="alert(1)">
            123
        </div>
        <script>
           (function () {
              function __canvasWM({
                container = document.body,
                width = '300px',
                height = '200px',
                textAlign = 'center',
                textBaseline = 'middle',
                font = "20px Microsoft Yahei",
                fillStyle = 'rgba(184, 184, 184, 0.6)',
                content = '水印',
                rotate = '45',
                zIndex = 10000,
                        opacity = 0.3
              } = {}) {
                const args = arguments[0];
                  const svgStr = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${width}">
                  <text x="50%" y="50%" dy="12px"
                    text-anchor="middle"
                    stroke="#000000"
                    stroke-width="1"
                    stroke-opacity="${opacity}"
                    fill="none"
                    transform="rotate(-45, 120 120)"
                    style="font-size: ${font};">
                    ${content}
                  </text>
                </svg>`;
                const base64Url = `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svgStr)))}`;
                const __wm = document.querySelector('.__wm');

                const watermarkDiv = __wm || document.createElement("div");
                
                const styleStr = `
                  position:fixed;
                  top:0;
                  left:0;
                  bottom:0;
                  right:0;
                  width:100%;
                  height:100%;
                  z-index:${zIndex};
                  pointer-events:none;
                  background-repeat:repeat;
                  background-image:url('${base64Url}')`;
        
                watermarkDiv.setAttribute('style', styleStr);
                watermarkDiv.classList.add('__wm');
        
                if (!__wm) {
                  container.style.position = 'relative';
                  container.insertBefore(watermarkDiv, container.firstChild);
                }
              if (typeof module != 'undefined' && module.exports) {  //CMD
                module.exports = __canvasWM;
              } else if (typeof define == 'function' && define.amd) { // AMD
                define(function () {
                  return __canvasWM;
                });
              } else {
                window.__canvasWM = __canvasWM;
              }
            })();
        
            // 调用
            __canvasWM({
              content: '水印123'
            });
        </script>
    </body>
</html>

Однако у трех вышеприведенных методов есть общая проблема: так как интерфейс генерирует элементы dom и покрывает их на странице, для тех, кто имеет некоторые знания о интерфейсе, вы можете найти элемент, на котором находится водяной знак, в инструментах разработчика и удалить весь элемент. , чтобы достичь цели удаления водяного знака на странице. В ответ на эту проблему я придумал очень глупый способ: установить таймер, чтобы каждые несколько секунд проверять, есть ли еще наш элемент водяного знака, был ли он изменен, и если это произойдет, если он изменится, снова выполните метод перезаписывания водяного знака. Увидел в сети другое решение: используйте MutationObserver

MutationObserver — это наблюдатель за изменениями, который буквально используется для наблюдения за изменениями узла. Mutation Observer API используется для отслеживания изменений DOM.Этот API может уведомлять о любых изменениях в DOM, таких как увеличение или уменьшение дочерних узлов, изменения атрибутов и изменения текстового содержимого.

Однако MutationObserver может отслеживать только такие изменения, как изменения атрибутов, изменения дочерних узлов и т. д. Невозможно отслеживать удаление самого себя, здесь требования могут быть выполнены путем наблюдения за родительским узлом. Реализация кода мониторинга:

const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
if (MutationObserver) {
  let mo = new MutationObserver(function () {
    const __wm = document.querySelector('.__wm');
    // 只在__wm元素变动才重新调用 __canvasWM
    if ((__wm && __wm.getAttribute('style') !== styleStr) || !__wm) {
      // 避免一直触发
      mo.disconnect();
      mo = null;
    __canvasWM(JSON.parse(JSON.stringify(args)));
    }
  });

  mo.observe(container, {
    attributes: true,
    subtree: true,
    childList: true
  })
}

}

общий код

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <div id="info" onclick="alert(1)">
                123
        </div>
        <script>
           (function () {
              function __canvasWM({
                container = document.body,
                width = '300px',
                height = '200px',
                textAlign = 'center',
                textBaseline = 'middle',
                font = "20px Microsoft Yahei",
                fillStyle = 'rgba(184, 184, 184, 0.6)',
                content = '水印',
                rotate = '45',
                zIndex = 10000
              } = {}) {
                const args = arguments[0];
                const canvas = document.createElement('canvas');
        
                canvas.setAttribute('width', width);
                canvas.setAttribute('height', height);
                const ctx = canvas.getContext("2d");
        
                ctx.textAlign = textAlign;
                ctx.textBaseline = textBaseline;
                ctx.font = font;
                ctx.fillStyle = fillStyle;
                ctx.rotate(Math.PI / 180 * rotate);
                ctx.fillText(content, parseFloat(width) / 2, parseFloat(height) / 2);
        
                const base64Url = canvas.toDataURL();
                const __wm = document.querySelector('.__wm');
        
                const watermarkDiv = __wm || document.createElement("div");
                const styleStr = `
                  position:fixed;
                  top:0;
                  left:0;
                  bottom:0;
                  right:0;
                  width:100%;
                  height:100%;
                  z-index:${zIndex};
                  pointer-events:none;
                  background-repeat:repeat;
                  background-image:url('${base64Url}')`;
        
                watermarkDiv.setAttribute('style', styleStr);
                watermarkDiv.classList.add('__wm');
        
                if (!__wm) {
                  container.style.position = 'relative';
                  container.insertBefore(watermarkDiv, container.firstChild);
                }
                
                const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
                if (MutationObserver) {
                  let mo = new MutationObserver(function () {
                    const __wm = document.querySelector('.__wm');
                    // 只在__wm元素变动才重新调用 __canvasWM
                    if ((__wm && __wm.getAttribute('style') !== styleStr) || !__wm) {
                      // 避免一直触发
                      mo.disconnect();
                      mo = null;
                    __canvasWM(JSON.parse(JSON.stringify(args)));
                    }
                  });
        
                  mo.observe(container, {
                    attributes: true,
                    subtree: true,
                    childList: true
                  })
                }
        
              }
        
              if (typeof module != 'undefined' && module.exports) {  //CMD
                module.exports = __canvasWM;
              } else if (typeof define == 'function' && define.amd) { // AMD
                define(function () {
                  return __canvasWM;
                });
              } else {
                window.__canvasWM = __canvasWM;
              }
            })();
        
            // 调用
            __canvasWM({
              content: '水印123'
            });
        </script>
    </body>
</html>



Конечно, после настройки MutationObserver, это только относительно безопасно.Можно еще пропустить наш мониторинг, отключив js через консоль.Вообще говоря, простое добавление водяного знака на фронтальную страницу всегда можно обойти какими-то хитрыми операциями. , остерегаться джентльмена, но не злодея, остерегаться мирянина, но не мирянина

image.png

4. Водяной знак изображения

Иногда нам нужно добавить водяной знак к изображению, чтобы указать авторство или другую информацию.Идея добавления водяного знака к изображению состоит в том, чтобы нарисовать изображение на холсте после того, как изображение успешно загружено, а затем нарисовать водяной знак в холст. После завершения передайте метод canvas.toDataUrl(), чтобы получить base64 и заменить исходный путь изображения

Код:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
    <div id="info" onclick="alert(1)">
        <img />
    </div>
    <script>
       (function() {
         function __picWM({
           url = '',
           textAlign = 'center',
           textBaseline = 'middle',
           font = "20px Microsoft Yahei",
           fillStyle = 'rgba(184, 184, 184, 0.8)',
           content = '水印',
           cb = null,
           textX = 100,
           textY = 30
         } = {}) {
           const img = new Image();
           img.src = url;
           img.crossOrigin = 'anonymous';
           img.onload = function() {
                 const canvas = document.createElement('canvas');
                 canvas.width = img.width;
                 canvas.height = img.height;
                 const ctx = canvas.getContext('2d');

                 ctx.drawImage(img, 0, 0);
                 ctx.textAlign = textAlign;
                 ctx.textBaseline = textBaseline;
                 ctx.font = font;
                 ctx.fillStyle = fillStyle;
                 ctx.fillText(content, img.width - textX, img.height - textY);

                 const base64Url = canvas.toDataURL();
                 cb && cb(base64Url);
           }
         }

        if (typeof module != 'undefined' && module.exports) {  //CMD
           module.exports = __picWM;
         } else if (typeof define == 'function' && define.amd) { // AMD
           define(function () {
                 return __picWM;
           });
         } else {
           window.__picWM = __picWM;
         }
             
       })();

       // 调用
       __picWM({
           url: './a.png',
           content: '水印水印',
           cb: (base64Url) => {
                 document.querySelector('img').src = base64Url
           },
       });

    </script>
    </body>
</html>

5. Расширение: Невидимые водяные знаки на изображениях

Для графических ресурсов явный водяной знак нарушит целостность изображения. В некоторых случаях мы хотим сохранить исходный стиль изображения, поэтому можем добавить скрытый водяной знак.

Простая идея реализации такова: информация о пикселях изображения хранит значение цвета RGB, а небольшое изменение значения компонента RGB неразличимо невооруженным глазом и не повлияет на распознавание изображения Мы можем использовать специальное значение RGB за картинку.Небольшие изменения в правилах.

The pixel data of the image can be obtained through canvas.getImageData(). First, draw the watermark image in the canvas to obtain its pixel data, and then obtain the pixel data of the original image through the canvas, and select one of R , G, and B, such as G, traverse the original image pixels, convert the G of the pixels with information corresponding to the watermark pixels to odd numbers, and convert the pixels corresponding to the watermark pixels without information to even numbers. After processing , convert them to base64 and replace them on the page. At this time, the watermark is invisible. Just add it. Under normal circumstances, this picture does not have a watermark, but after the corresponding rules (the decryption rule corresponding to the above example is: traverse the corresponding G in the pixel data of the picture, and set its rgba to 0, 255 for odd numbers, 0, the even number is set to 0, 0, 0) after the decryption process, the watermark can be видимый.

Таким образом, когда пользователь получает изображение, делая снимки экрана, конвертируя формат после сохранения изображения и т. д., значение цвета изображения может измениться, что повлияет на эффект водяного знака. Реализация кода водяного знака:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <canvas id="canvasText" width="256" height="256"></canvas>
        <canvas id="canvas" width="256" height="256"></canvas>
        
        <script>
            var ctx = document.getElementById('canvas').getContext('2d');
            var ctxText = document.getElementById('canvasText').getContext('2d');
            
            var textData;
            ctxText.font = '30px Microsoft Yahei';
            ctxText.fillText('水印', 60, 130);
            textData = ctxText.getImageData(0, 0, ctxText.canvas.width, ctxText.canvas.height).data;
            
            var img = new Image();
            var originalData;
            img.onload = function() {
                ctx.drawImage(img, 0, 0);
                // 获取指定区域的canvas像素信息
                originalData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
                console.log(originalData);
                    mergeData(textData,'G')
                    console.log(document.getElementById('canvas').toDataURL())
            };
            img.src = './aa.jpeg';
            
            var mergeData = function(newData, color){
                var oData = originalData.data;
                var bit, offset;  
             
                switch(color){
                    case 'R':
                        bit = 0;
                        offset = 3;
                        break;
                    case 'G':
                        bit = 1;
                        offset = 2;
                        break;
                    case 'B':
                        bit = 2;
                        offset = 1;
                        break;
                }
             
                for(var i = 0; i < oData.length; i++){
                    if(i % 4 == bit){
                        // 只处理目标通道
                        if(newData[i + offset] === 0 && (oData[i] % 2 === 1)){
                            // 没有水印信息的像素,将其对应通道的值设置为偶数
                            if(oData[i] === 255){
                                oData[i]--;
                            } else {
                                oData[i]++;
                            }
                        } else if (newData[i + offset] !== 0 && (oData[i] % 2 === 0)){
                            // 有水印信息的像素,将其对应通道的值设置为奇数
                            if(oData[i] === 255){
                                oData[i]--;
                            } else {
                                oData[i]++;
                            }
                        }
                    }
                }
                ctx.putImageData(originalData, 0, 0);
            }
            
        </script>
    </body>
</html>

Показать реализацию кода водяного знака:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <canvas id="canvas" width="256" height="256"></canvas>
        
        <script>
            var ctx = document.getElementById('canvas').getContext('2d');
            var img = new Image();
            var originalData;
            img.onload = function() {
                ctx.drawImage(img, 0, 0);
                // 获取指定区域的canvas像素信息
                originalData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
                console.log(originalData);
                    processData(originalData)
            };
            img.src = './a.jpg';
            
            var processData = function(originalData){
                var data = originalData.data;
                for(var i = 0; i < data.length; i++){
                    if(i % 4 == 1){
                        if(data[i] % 2 === 0){
                            data[i] = 0;
                        } else {
                            data[i] = 255;
                        }
                    } else if(i % 4 === 3){
                        // alpha通道不做处理
                        continue;
                    } else {
                        // 关闭其他分量,不关闭也不影响答案,甚至更美观 o(^▽^)o
                        data[i] = 0;
                    }
                }
                // 将结果绘制到画布
                ctx.putImageData(originalData, 0, 0);
            }
                
        </script>
    </body>
</html>


Это относительно простая реализация, если вы хотите узнать больше, вы можете обратиться кНаггетс.Талант/пост/691793…

4. Справочные документы

1. Слепое нанесение водяных знаков и стеганография изображений:Наггетс.Талант/пост/691793…

2. Unspeakable secrets - стеганография изображений, которую также можно воспроизвести в интерфейсе:woohoo.alloy team.com/2016/03/IMA…

3. Схема генерации водяного знака для внешнего интерфейса (водяной знак веб-страницы + водяной знак изображения):nuggets.capable/post/684490…