许多数据库存储了以blob或文件形式保存的位图,其中包括照片、文档扫描、医学图像等。当这些位图被各种数据库客户端和应用程序检索时,为了日后的识别和追踪,有时需要在检索时为它们添加唯一的水印。在某些情况下,人们甚至希望这些水印是不可见的。
这种动态的位图操作可以很容易地通过可编程的数据库代理完成,无需对持久化存储的位图进行任何修改。
这种方法有以下好处:
- 水印可以为每次检索进行定制,并可以包含有关日期、时间、用户身份、IP地址等信息。
- 图像处理由代理完成,这样不会给数据库带来额外的负载。
- 不需要更改数据库或数据库客户端。
最终结果
给定一个存储在数据库中的位图,例如下图:
可编程的数据库代理可以在发送到客户端的途中修改位图,以包含任何所需信息的水印,例如下图:
工作原理
这个架构很简单:它并不依赖于传统的数据库客户端与服务器之间的直接连接,而是采用了一种不同的方式:
客户端连接到代理,代理连接到服务器:
然后,代理可以在检索位图时根据需要处理这些位图。
例如,它只能为某些位图添加水印,也可以根据具体情况使用不同样式的水印。存储在数据库中的位图完全不受影响:它们在转发给客户端时被动态修改。
优点
- 客户和数据库对此一无所知——这对他们来说是完全透明的。
- 每当图像被检索时,都可以为其添加独特的水印(例如,日期/时间、用户名、客户端的IP地址等)。
- 数据库服务器上不会增加额外的负载。
缺点
- 由于添加了代理,系统变得更加复杂。
- 延迟会增加(通常是适度的),主要取决于图像的大小,但应该与替代方案进行比较。
实例
使用代理,可以创建一个简单的过滤器,为某些位图添加水印。
如果假设数据库包含一个名为images的表,其中包含一个名为bitmap的列,类型为blob或varbinary(取决于数据库),可以在代理中使用以下参数创建一个结果集过滤器:
Query pattern: regex:select.*from.*images.*
以下一些JavaScript代码(也使用底层Java引擎):
JavaScript
// Get the value of the bitmap column as a byte stream
let stream = context.packet.getJavaStream("bitmap");
if (stream === null) {
return;
}
// The text to use as watermark
const now = new Date();
const watermark = "Retrieved by " + context.connectionContext.userName +
" on " + now.getFullYear() + "/" + (now.getMonth()+1) + "/" + now.getDate();
// Read the bitmap
const ImageIO = Java.type("javax.imageio.ImageIO");
let img = ImageIO.read(stream);
// Create the Graphics to draw the text
let g = img.createGraphics();
const Color = Java.type("java.awt.Color");
g.setColor(new Color(255, 255, 0, 150));
const Font = Java.type("java.awt.Font");
g.setFont(new Font("sans-serif", Font.BOLD, 16));
// Draw the text at the bottom of the bitmap
let textRect = textFont.getStringBounds(watermark, g.getFontRenderContext());
g.drawString(watermark, (img.getWidth() / 2) - (textRect.getWidth() / 2),
img.getHeight() - (textRect.getHeight() / 2));
// Write the bitmap to the column value
const ByteArrayOutputStream = Java.type("java.io.ByteArrayOutputStream");
let outStream = new ByteArrayOutputStream();
ImageIO.write(img, "png", outStream);
context.packet.bitmap = outStream.toByteArray();
启用这个过滤器之后,从这个表中检索的位图将包括一个水印,其中包含数据库用户的名称和时间戳。
数据库永远不会受到影响:存储在数据库中的位图完全不变,它们是在发送给客户端时即时修改的。
显然,可以选择性地为位图添加水印,可以根据任何相关因素更改水印的文本,还可以通过字体、颜色、定位、透明度等因素添加水印。有关详细信息,参见这个示例。
秘密水印
在某些情况下,可能需要以肉眼不可见的方式标记位图。一种简单的方法是编辑图像的元数据,但如果需要更微妙的东西,可以使用隐写术在位图中分发秘密消息,使其难以被检测到。
可以修改以上的示例以使用Adumbra库:
复制
// Get the value of the bitmap column as a byte stream
let inStream = context.packet.getJavaStream("bitmap");
if (inStream === null) {
return;
}
// The hidden message
const now = new Date();
const message = "Retrieved by " + context.connectionContext.userName +
" on " + now.getFullYear() + "/" + (now.getMonth()+1) + "/" + now.getDate();
const messageBytes = context.utils.getUTF8BytesForString(message);
const keyBytes = context.utils.getUTF8BytesForString("This is my secret key");
// Hide the message in the bitmap
const Encoder = Java.type("com.galliumdata.adumbra.Encoder");
const ByteArrayOutputStream = Java.type("java.io.ByteArrayOutputStream");
let outStream = new ByteArrayOutputStream();
let encoder = new Encoder(1);
encoder.encode(inStream, outStream, "png", messageBytes, keyBytes);
context.packet.bitmap = outStream.toByteArray();
有了这一点,提供给客户端修改后的位图将包含一个难以检测的秘密水印,且在没有密钥的情况下几乎无法提取。
水印技术还有哪些用途?
这种水印技术也可以应用于位图以外的文档:
- 像PDF和MS Word这样的文档可以被赋予一些额外的元数据,或者它们可以被赋予一个可见或不可见的水印。可以参考PDF文档的这个示例。
- 所有的文本文档可以巧妙地使用水印技术进行标记,例如改变间距、拼写、布局、字体和颜色、零宽度字符等。
- 所有能够在不失去任何重要意义的情况下进行微小更改的数字文档,例如位图、音频文件和样本集,都能够以类似的方式进行修改。
- 事实上,整个数据集可以通过巧妙地修改数据的一些非关键方面来添加水印,从而有可能在以后识别这些数据集并确切地知道它们的来源。这超出了本文的范围,但是有许多方法可以使数据追溯到其起源。
结论
当需要从数据库中检索位图或文档时,并且每次检索都需要一个定制的水印,这里展示的技术是一种可靠的方法,它避免了给数据库带来任何额外的负担,并且不需要对客户端或服务器进行任何更改。