> 技术文档 > AIGC安全要求针对图片的隐式标识功能实现_java 图片添加隐式标识

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图加隐式标识

  1. 格式设计差异
    EXIF 是为 JPEG、TIFF 等图像格式设计的元数据标准,用于存储相机参数(如光圈、快门速度)、拍摄时间、GPS 位置等信息。而 GIF 是一种较早的格式(1987年诞生),主要用于存储动态或静态图像,其结构不支持嵌入 EXIF 数据块。

  2. GIF 的元数据能力有限
    GIF 文件可以通过扩展块(如注释块 Comment Extension)存储简单的文本信息,但这些是纯文本字段,并非结构化的 EXIF 数据。例如,某些工具可以添加作者、版权信息到 GIF 的注释中,但无法像 EXIF 那样定义复杂的键值对。

  3. 实际应用场景
    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(); } }