diff --git a/openpdf/src/main/java/com/lowagie/text/pdf/BaseFont.java b/openpdf/src/main/java/com/lowagie/text/pdf/BaseFont.java index c381ec666..de3680f52 100644 --- a/openpdf/src/main/java/com/lowagie/text/pdf/BaseFont.java +++ b/openpdf/src/main/java/com/lowagie/text/pdf/BaseFont.java @@ -54,6 +54,7 @@ import java.io.IOException; import java.io.InputStream; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.HashMap; import java.util.StringTokenizer; @@ -336,6 +337,11 @@ public abstract class BaseFont { */ protected IntHashtable specialMap; + /** + * Used to build a randomized prefix for a subset name + */ + protected SecureRandom secureRandom; + static { BuiltinFonts14.put(COURIER, PdfName.COURIER); BuiltinFonts14.put(COURIER_BOLD, PdfName.COURIER_BOLD); @@ -1353,14 +1359,37 @@ public boolean isFontSpecific() { * * @return the subset prefix */ - public static String createSubsetPrefix() { + protected String createSubsetPrefix() { String s = ""; + SecureRandom secureRandom = getSecureRandom(); for (int k = 0; k < 6; ++k) { - s += (char) (Math.random() * 26 + 'A'); + s += (char) (secureRandom.nextDouble() * 26 + 'A'); } return s + "+"; } + /** + * Returns a defined SecureRandom implementation. + * If nothing is set, returns a new + * + * @return {@link SecureRandom} + */ + protected SecureRandom getSecureRandom() { + if (secureRandom != null) { + return secureRandom; + } + return new SecureRandom(); + } + + /** + * Sets the SecureRandom instance to be used for a subset's prefix generation + * + * @param secureRandom {@link SecureRandom} + */ + public void setSecureRandom(SecureRandom secureRandom) { + this.secureRandom = secureRandom; + } + /** * Gets the Unicode character corresponding to the byte output to the pdf * stream. diff --git a/openpdf/src/main/java/com/lowagie/text/pdf/PdfReader.java b/openpdf/src/main/java/com/lowagie/text/pdf/PdfReader.java index a5a20726c..c93af32b5 100644 --- a/openpdf/src/main/java/com/lowagie/text/pdf/PdfReader.java +++ b/openpdf/src/main/java/com/lowagie/text/pdf/PdfReader.java @@ -2650,7 +2650,7 @@ public int shuffleSubsetNames() { String s = getSubsetPrefix(dic); if (s == null) continue; - String ns = BaseFont.createSubsetPrefix() + s.substring(7); + String ns = createRandomSubsetPrefix() + s.substring(7); PdfName newName = new PdfName(ns); dic.put(PdfName.BASEFONT, newName); setXrefPartialObject(k, dic); @@ -2670,7 +2670,7 @@ public int shuffleSubsetNames() { String sde = getSubsetPrefix(desc); if (sde == null) continue; - String ns = BaseFont.createSubsetPrefix(); + String ns = createRandomSubsetPrefix(); if (s != null) dic.put(PdfName.BASEFONT, new PdfName(ns + s.substring(7))); setXrefPartialObject(k, dic); @@ -2686,6 +2686,20 @@ public int shuffleSubsetNames() { return total; } + /** + * Creates a unique subset prefix to be added to the font name when the font + * is embedded and subset. + * + * @return the subset prefix + */ + private String createRandomSubsetPrefix() { + String s = ""; + for (int k = 0; k < 6; ++k) { + s += (char) (Math.random() * 26 + 'A'); + } + return s + "+"; + } + /** * Finds all the fonts not subset but embedded and marks them as subset. * @@ -2709,7 +2723,7 @@ public int createFakeFontSubsets() { s = getFontName(dic); if (s == null) continue; - String ns = BaseFont.createSubsetPrefix() + s; + String ns = createRandomSubsetPrefix() + s; PdfDictionary fd = (PdfDictionary) getPdfObjectRelease(dic .get(PdfName.FONTDESCRIPTOR)); if (fd == null) diff --git a/openpdf/src/test/java/com/lowagie/text/pdf/SubsetPrefixCreationTest.java b/openpdf/src/test/java/com/lowagie/text/pdf/SubsetPrefixCreationTest.java new file mode 100644 index 000000000..fd7da0110 --- /dev/null +++ b/openpdf/src/test/java/com/lowagie/text/pdf/SubsetPrefixCreationTest.java @@ -0,0 +1,50 @@ +package com.lowagie.text.pdf; + +import org.apache.commons.io.IOUtils; +import org.bouncycastle.crypto.prng.FixedSecureRandom; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.security.SecureRandom; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +/** + * See : https://github.com/LibrePDF/OpenPDF/issues/623 + */ +public class SubsetPrefixCreationTest { + + @Test + public void createSubsetPrefixTest() throws Exception { + BaseFont font = BaseFont.createFont("LiberationSerif-Regular.ttf", BaseFont.IDENTITY_H, + BaseFont.EMBEDDED,true, getFontByte("LiberationSerif-Regular.ttf"), null); + assertNotEquals(font.createSubsetPrefix(), font.createSubsetPrefix()); + + byte[] baseSeed = new SecureRandom().generateSeed(512); + // init deterministic SecureRandom with a custom base seed + SecureRandom secureRandom = new FixedSecureRandom(baseSeed); + font.setSecureRandom(secureRandom); + assertNotEquals(font.createSubsetPrefix(), font.createSubsetPrefix()); // still different, as FixedSecureRandom generates a new random on each step + + SecureRandom secureRandomOne = new FixedSecureRandom(baseSeed); + font.setSecureRandom(secureRandomOne); + + String subsetPrefixOne = font.createSubsetPrefix(); + + // re-init FixedSecureRandom for deterministic generation + SecureRandom secureRandomTwo = new FixedSecureRandom(baseSeed); + font.setSecureRandom(secureRandomTwo); + + String subsetPrefixTwo = font.createSubsetPrefix(); + assertEquals(subsetPrefixOne, subsetPrefixTwo); // the desired deterministic behavior + } + + private byte[] getFontByte(String fileName) throws IOException { + try (InputStream stream = BaseFont.getResourceStream(fileName, null)) { + return IOUtils.toByteArray(stream); + } + } + +}