AIGC安全要求针对图片的隐式标识功能实现_java 图片添加隐式标识
随着《关于印发《人工智能生成合成内容标识办法》的通知_中央网络安全和信息化委员会办公室》最后实施时间的来临,技术人员有大量的工作要进行。
在《网络安全标准实践指南》中明确的要求
1、对于 JPEG/JPG、HEIF、PNG(自 1.5.0 起)、Webp 等格式,将元数据标识写入 “UserComment” 字段。
2、基于 Chunk 结构的图片文件的元数据方案
可通过对 PNG 允许自定义块(私有块)用来放置 AIGC 标识,定义其块类型标识符为 AIGC,块内容放置 AIGC 标识内容。创建一个 PNG 文本块(tEXt chunk),设 置 该 文 本 块 的 key 是 “AIGC” , value 是json字符串即可,如下
{\"Label\":\"value1\",\"ContentProducer\":\"value2\",\"ProduceID\":\"value3\",\"ReservedCode1\":\"value4\",\"ContentPropagator\":\"value5\",\"PropagateID\":\"value6\",\"ReservedCode2:\":\"value7\"}。
3、指南中未提及gif图,也未给出示例,但肯定也有要求。
4、指南中提供了Android,iOS, python的示例,但未提供java版。
下面直接上干货,java方式处理 jpg, jpeg,png,gif图 增加隐式标识的实现。
1、jpg, jpeg图片加隐式标识
先要引入 第三方commons-imaging 依赖包
org.apache.commons commons-imaging 1.0-alpha3
import org.apache.commons.imaging.ImageReadException;import org.apache.commons.imaging.ImageWriteException;import org.apache.commons.imaging.Imaging;import org.apache.commons.imaging.common.ImageMetadata;import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;import org.apache.commons.imaging.formats.jpeg.exif.ExifRewriter;import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;import org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants;import org.apache.commons.imaging.formats.tiff.write.TiffOutputDirectory;import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet; /** * 向jpg, jpeg 图片的EXIF UserComment字段写入AIGC信息 * @param sourceFile 源图片文件 * @param destFile 目标图片文件(可以与源文件相同即进行覆盖) * @param comment 要写入的注释内容 * @throws IOException * @throws ImageReadException * @throws ImageWriteException */ public static void writeExifUserComment(File sourceFile, File destFile, String comment) throws IOException, ImageReadException, ImageWriteException { // 获取现有的EXIF数据(如果有) TiffOutputSet outputSet = getOrCreateOutputSet(sourceFile); // 获取EXIF目录(如果没有则创建) TiffOutputDirectory exifDirectory = outputSet.getOrCreateExifDirectory(); // 删除现有的UserComment(如果存在) exifDirectory.removeField(ExifTagConstants.EXIF_TAG_USER_COMMENT); // 创建新的UserComment字段 // UserComment的格式是: ASCII码标识符(8字节) + 实际内容 //在 EXIF 标准中,UserComment(标签 0x9286)的存储方式比较特殊,它并不是直接存储纯文本,而是采用了一种编码标识符+实际内容的结构。这就是为什么你在代码中看到的 UserComment 数据看起来像是数字(字节数组),而不是直接可读的字符串。 // 通常使用\"ASCII\\0\\0\\0\"作为标识符// byte[] identifier = \"ASCII\\0\\0\\0\".getBytes(StandardCharsets.US_ASCII);// byte[] commentBytes = comment.getBytes(StandardCharsets.UTF_8);// byte[] userCommentValue = new byte[identifier.length + commentBytes.length];//// System.arraycopy(identifier, 0, userCommentValue, 0, identifier.length);// System.arraycopy(commentBytes, 0, userCommentValue, identifier.length, commentBytes.length);// System.out.println(new String(userCommentValue, StandardCharsets.US_ASCII).trim());// TiffOutputField userCommentField = TiffOutputField.createOffsetField(// ExifTagConstants.EXIF_TAG_USER_COMMENT,// userCommentValue.length, // userCommentValue); // exifDirectory.add(userCommentField); exifDirectory.add(ExifTagConstants.EXIF_TAG_USER_COMMENT, comment); // 写入修改后的EXIF数据 try (FileOutputStream fos = new FileOutputStream(destFile)) { new ExifRewriter().updateExifMetadataLossless(sourceFile, fos, outputSet); } } private static TiffOutputSet getOrCreateOutputSet(File sourceFile) throws IOException, ImageReadException, ImageWriteException { // 尝试获取现有的EXIF数据 ImageMetadata metadata = Imaging.getMetadata(sourceFile); JpegImageMetadata jpegMetadata = (metadata instanceof JpegImageMetadata) ? (JpegImageMetadata) metadata : null; if (jpegMetadata != null) { TiffImageMetadata exif = jpegMetadata.getExif(); if (exif != null) { return exif.getOutputSet(); } } // 如果没有EXIF数据,创建一个新的 return new TiffOutputSet(); }
2、png图片加隐式标识
使用java标准版自带的 Java ImageIO 即可实现
/** * png图片写入AIGC自定义信息 * @param inputFilePath png原图路径 * @param outputFilePath 修改元信息后新图的路径 * @param comment 要写入的注释内容 * @return * @throws Exception */ public static void writePngCustomData(String inputFilePath, String outputFilePath, String key, String comment) throws Exception { ImageWriter writer = ImageIO.getImageWritersByFormatName(\"png\").next(); ImageWriteParam writeParam = writer.getDefaultWriteParam(); ImageTypeSpecifier typeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB); //添加metadata IIOMetadata metadata = writer.getDefaultImageMetadata(typeSpecifier, writeParam); IIOMetadataNode textEntry = new IIOMetadataNode(\"tEXtEntry\"); textEntry.setAttribute(\"keyword\", key); textEntry.setAttribute(\"value\", comment); IIOMetadataNode text = new IIOMetadataNode(\"tEXt\"); text.appendChild(textEntry); IIOMetadataNode root = new IIOMetadataNode(\"javax_imageio_png_1.0\"); root.appendChild(text); metadata.mergeTree(\"javax_imageio_png_1.0\", root); //写入数据 ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageOutputStream stream = ImageIO.createImageOutputStream(baos); writer.setOutput(stream); BufferedImage bufferImage = ImageIO.read(new File(inputFilePath)); writer.write(metadata, new IIOImage(bufferImage, null, metadata), writeParam); stream.close(); Files.write(Paths.get(outputFilePath), baos.toByteArray()); } /** * 读取指定png图片的指定key的元信息 * @param outputFilePath * @param key * @return * @throws IOException */ public static String readCustomData(String outputFilePath, String key) throws IOException { byte[] imageData = Files.readAllBytes(Paths.get(outputFilePath)); ImageReader imageReader = ImageIO.getImageReadersByFormatName(\"png\").next(); imageReader.setInput(ImageIO.createImageInputStream(new ByteArrayInputStream(imageData)), true); // read metadata of first image IIOMetadata metadata = imageReader.getImageMetadata(0); //this cast helps getting the contents PNGMetadata pngmeta = (PNGMetadata) metadata; NodeList childNodes = pngmeta.getStandardTextNode().getChildNodes(); for (int i = 0; i \" + value); if(key.equals(keyword)){ return value; } } return null; }
3、gif图加隐式标识
-
格式设计差异:
EXIF 是为 JPEG、TIFF 等图像格式设计的元数据标准,用于存储相机参数(如光圈、快门速度)、拍摄时间、GPS 位置等信息。而 GIF 是一种较早的格式(1987年诞生),主要用于存储动态或静态图像,其结构不支持嵌入 EXIF 数据块。 -
GIF 的元数据能力有限:
GIF 文件可以通过扩展块(如注释块Comment Extension
)存储简单的文本信息,但这些是纯文本字段,并非结构化的 EXIF 数据。例如,某些工具可以添加作者、版权信息到 GIF 的注释中,但无法像 EXIF 那样定义复杂的键值对。 -
实际应用场景:
GIF 通常用于网络动画或简单图形,对摄影元数据的需求较低。即使工具强行将 EXIF 写入 GIF,大多数软件也无法识别或读取。
因此我们通过操作 Comment Extension(注释扩展块)来实现隐式标识的写入
/** * gif图片写入AIGC信息,每一帧都写入 * @param inputFilePath png原图路径 * @param outputFilePath 修改元信息后新图的路径 * @param comment 要写入的注释内容 * @return * @throws Exception */ public static void writeGifCustomData(String inputFilePath, String outputFilePath, String comment) throws Exception { File gifFile = new File(inputFilePath); File outputGif = new File(outputFilePath); try (ImageInputStream iis = ImageIO.createImageInputStream(gifFile)) { Iterator readers = ImageIO.getImageReaders(iis); if (!readers.hasNext()) { System.out.println(\"No GIF reader found!\"); return; } ImageReader reader = readers.next(); reader.setInput(iis); //1、 获取 GIF 帧数 int frameCount = reader.getNumImages(true); BufferedImage[] inputFrames = new BufferedImage[frameCount]; IIOMetadataNode[] inputNodes = new IIOMetadataNode[frameCount]; for (int i = 0; i 0) { // 修改现有注释 IIOMetadataNode commentNode = (IIOMetadataNode) nodes.item(0).getFirstChild(); commentNode.setAttribute(\"value\", comment); } else { // 添加新注释 IIOMetadataNode commentExtension = new IIOMetadataNode(\"CommentExtensions\"); IIOMetadataNode commentNode = new IIOMetadataNode(\"CommentExtension\"); commentNode.setAttribute(\"value\", comment); commentExtension.appendChild(commentNode); rootNode.appendChild(commentExtension); } inputNodes[i] = rootNode; } //4、 写入新GIF文件 try (ImageOutputStream ios = ImageIO.createImageOutputStream(outputGif)) { ImageWriter writer = ImageIO.getImageWritersByFormatName(\"gif\").next(); ImageWriteParam params = writer.getDefaultWriteParam(); writer.setOutput(ios); IIOMetadata streamMetadata = writer.getDefaultStreamMetadata(null); writer.prepareWriteSequence(streamMetadata); // 写入所有帧(保留原延迟时间等元数据) for (int i = 0; i < inputFrames.length; i++) { IIOMetadata outputMetadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromBufferedImageType(1), params); outputMetadata.mergeTree(\"javax_imageio_gif_image_1.0\", inputNodes[i]); writer.writeToSequence(new IIOImage(inputFrames[i], null, outputMetadata), params); } writer.endWriteSequence(); writer.dispose(); } } } /** * 读取指定gif图片的元信息 * @param outputFilePath * @return * @throws IOException */ public static void readGifCustomData(String outputFilePath) { //读取gif图的某一帧的元信息 File inputFile = new File(outputFilePath); try (ImageInputStream iis = ImageIO.createImageInputStream(inputFile)) { Iterator readers = ImageIO.getImageReaders(iis); if (!readers.hasNext()) { System.out.println(\"No GIF reader found!\"); return; } ImageReader reader = readers.next(); reader.setInput(iis); System.out.println(reader.getNumImages(true)); // 获取 GIF 的元数据 IIOMetadata inputMetadata = reader.getImageMetadata(0); IIOMetadataNode rootNode = (IIOMetadataNode) inputMetadata.getAsTree(\"javax_imageio_gif_image_1.0\"); // 遍历所有节点,查找 CommentExtensions NodeList nodes = rootNode.getChildNodes(); for (int i = 0; i \"); Node childNode = node.getFirstChild(); if(childNode!=null) { System.out.println(node.getNodeName() + \" -> \"+childNode.getNodeName()+\" ->\"); Node commentItemNode = childNode.getAttributes().getNamedItem(\"value\"); if(commentItemNode!=null) { String commentValue = commentItemNode.getNodeValue(); System.out.println(node.getNodeName() + \" -> \"+ childNode.getNodeName() +\" -> \" +commentItemNode.getNodeName() + \" -> \" + commentValue); } } } }catch(Exception e) { e.printStackTrace(); } }