<div dir="ltr"><div dir="ltr">Martin, thanks for this!  I had never heard of FIPS but this certainly seems harmless and something we could include in an upcoming release.  I'll wait for my colleague Robin to review.<div><br></div><div><span class="gmail_signature_prefix">--</span><br><div dir="ltr" class="gmail_signature"><div dir="ltr">Andy Robinson<br>Managing Director, ReportLab<br></div><div dir="ltr"><br></div></div></div></div><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">On Mon, 30 Jun 2025 at 02:14, Martin Renters via reportlab-users <<a href="mailto:reportlab-users@lists2.reportlab.com">reportlab-users@lists2.reportlab.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>First of all, I’d like to thank the ReportLab people for releasing such a fabulous open-source toolkit. It truly makes generating PDFs so much easier!<div><br></div><div>The problem I’m reporting affects users that have enabled FIPS mode on their systems. In my particular case, it is a RHEL 9.6 system. When in FIPS mode, certain cryptographic functions are not permitted because of weaknesses in the algorithms. In the case of ReportLab, this would be the MD5 function which is used to calculate a document ID and is also used to track fonts used, and a few other things, as well as in the PDF encryption functions themselves.</div><div><br></div><div>When in FIPS mode, running a program will cause an exception similar to:</div><div><br></div><div><p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">Traceback (most recent call last):</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">  File "/opt/dftoolkit/bin/annotateCRF", line 82, in main</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">    annotate.build_pdf(plate_filter=args.plates)</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">  File "//opt/dftoolkit/lib/python3.9/site-packages/dftoolkit/annotate.py", line 549, in build_pdf</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">    self.doc.build(flowables)</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">  File "//opt/dftoolkit/lib/python3.9/site-packages/reportlab/platypus/doctemplate.py", line 1062, in build</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">    self._startBuild(filename,canvasmaker)</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">  File "//opt/dftoolkit/lib/python3.9/site-packages/reportlab/platypus/doctemplate.py", line 1032, in _startBuild</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">    self.canv = self._makeCanvas(filename=filename,canvasmaker=canvasmaker)</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">  File "//opt/dftoolkit/lib/python3.9/site-packages/reportlab/platypus/doctemplate.py", line 992, in _makeCanvas</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">    canv = canvasmaker(filename or self.filename,</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">  File "//opt/dftoolkit/lib/python3.9/site-packages/reportlab/pdfgen/canvas.py", line 320, in __init__</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">    self._doc = pdfdoc.PDFDocument(compression=pageCompression,</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">  File "//opt/dftoolkit/lib/python3.9/site-packages/reportlab/pdfbase/pdfdoc.py", line 137, in __init__</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">    sig = self.signature = md5()</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">ValueError: [digital envelope routines] unsupported</span></p></div><div><br></div><div>Most of the uses of MD5, aside from the use in the actual PDF encryption, appear not to be security related. In Python 3.9, the hashlib functions can be passed a keyword argument called ‘usedforsecurity’ (defaulting to True) that can be set to False if the hash algorithm isn’t used for security purposes. This allows the use of MD5 hashes even on FIPS enabled systems and allows ReportLab to successfully generate PDFs. Passing this keyword argument on the older Python 3.6.8 doesn’t cause any ill effects.</div><div><br></div><div>A small test program demonstrating the hashlib problem on a FIPS enabled system, and the fix using usedforsecurity=False is as follows:</div><div><br></div><div><span style="font-family:Menlo;font-size:11px">$ more md5test.py</span></div><div><p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">from hashlib import md5</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">m = md5()</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">m.update(b'This is a test')</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">print(m.hexdigest())</span></p><p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures"><br></span></p><p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">$ python md5test.py</span></p><p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">Traceback (most recent call last):</span></p><p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">  File "/home/martin/md5test.py", line 2, in <module></span></p><p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">    m = md5()</span></p><p style="margin:0px;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">



</span></p><p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">ValueError: [digital envelope routines] unsupported</span></p><p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures"><br></span></p></div><div>Changing the m=md5() to m=md5(usedforsecurity=False) causes it to run successfully:</div><div><br></div><div><p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">$ more md5test.py</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">from hashlib import md5</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">m = md5(usedforsecurity=False)</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">m.update(b'This is a test')</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">print(m.hexdigest())</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures"><br></span></p><p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">$ python md5test.py</span></p>
<p style="margin:0px;font-style:normal;font-variant-caps:normal;font-stretch:normal;font-size:11px;line-height:normal;font-family:Menlo;font-size-adjust:none;font-kerning:auto;font-variant-alternates:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-feature-settings:normal"><span style="font-variant-ligatures:no-common-ligatures">ce114e4501d2f4e2dcea3e17b546f339</span></p></div><div><span style="font-variant-ligatures:no-common-ligatures"><br></span></div><div>Attached is a patch that adds the usedforsecurity=False parameter in the cases I believe are not security related. Alternatively, a different hash function permitted by FIPS could be used. The actual PDF encryption code isn’t modified by this patch and it will probably require the use of different algorithms to be FIPS compatible.</div><div><br></div><div>Thanks for considering this patch,</div><div><br></div><div>Martin</div><div><br></div><div><div><div>diff -ru ../venv3.9/lib/python3.9/site-packages/reportlab/lib/fontfinder.py reportlab/lib/fontfinder.py</div><div>--- ../venv3.9/lib/python3.9/site-packages/reportlab/lib/fontfinder.py<span style="white-space:pre-wrap"> </span>2025-06-27 12:37:23.507252855 -0400</div><div>+++ reportlab/lib/fontfinder.py<span style="white-space:pre-wrap">       </span>2025-06-29 15:08:52.470092313 -0400</div><div>@@ -216,7 +216,7 @@</div><div>         """Base this on the directories...same set of directories</div><div>         should give same cache"""</div><div>         fsEncoding = self._fsEncoding</div><div>-        hash = md5(b''.join(asBytes(_,enc=fsEncoding) for _ in sorted(self._dirs))).hexdigest()</div><div>+        hash = md5(b''.join(asBytes(_,enc=fsEncoding) for _ in sorted(self._dirs)),usedforsecurity=False).hexdigest()</div><div>         from reportlab.lib.utils import get_rl_tempfile</div><div>         fn = get_rl_tempfile('fonts_%s.dat' % hash)</div><div>         return fn</div><div>diff -ru ../venv3.9/lib/python3.9/site-packages/reportlab/lib/utils.py reportlab/lib/utils.py</div><div>--- ../venv3.9/lib/python3.9/site-packages/reportlab/lib/utils.py<span style="white-space:pre-wrap">     </span>2025-06-27 12:37:23.512252786 -0400</div><div>+++ reportlab/lib/utils.py<span style="white-space:pre-wrap">    </span>2025-06-29 15:08:52.473092276 -0400</div><div>@@ -55,7 +55,7 @@</div><div> _rl_NoneType=type(None)</div><div> strTypes = (str,bytes)</div><div> def _digester(s):</div><div>-    return md5(s if isBytes(s) else s.encode('utf8')).hexdigest()</div><div>+    return md5(s if isBytes(s) else s.encode('utf8'),usedforsecurity=False).hexdigest()</div><div> </div><div> def asBytes(v,enc='utf8'):</div><div>     if isinstance(v,bytes): return v</div><div>diff -ru ../venv3.9/lib/python3.9/site-packages/reportlab/pdfbase/cidfonts.py reportlab/pdfbase/cidfonts.py</div><div>--- ../venv3.9/lib/python3.9/site-packages/reportlab/pdfbase/cidfonts.py<span style="white-space:pre-wrap">    </span>2025-06-27 12:37:23.519252690 -0400</div><div>+++ reportlab/pdfbase/cidfonts.py<span style="white-space:pre-wrap">     </span>2025-06-29 15:08:52.668089932 -0400</div><div>@@ -85,7 +85,7 @@</div><div>                 self.parseCMAPFile(name)</div><div> </div><div>     def _hash(self, text):</div><div>-        hasher = md5()</div><div>+        hasher = md5(usedforsecurity=False)</div><div>         hasher.update(text)</div><div>         return hasher.digest()</div><div> </div><div>diff -ru ../venv3.9/lib/python3.9/site-packages/reportlab/pdfbase/pdfdoc.py reportlab/pdfbase/pdfdoc.py</div><div>--- ../venv3.9/lib/python3.9/site-packages/reportlab/pdfbase/pdfdoc.py<span style="white-space:pre-wrap">      </span>2025-06-27 12:37:23.520252676 -0400</div><div>+++ reportlab/pdfbase/pdfdoc.py<span style="white-space:pre-wrap">       </span>2025-06-29 15:08:52.672089884 -0400</div><div>@@ -134,7 +134,7 @@</div><div>         self.setCompression(compression)</div><div>         self._pdfVersion = pdfVersion</div><div>         # signature for creating PDF ID</div><div>-        sig = self.signature = md5()</div><div>+        sig = self.signature = md5(usedforsecurity=False)</div><div>         sig.update(b"a reportlab document")</div><div>         self._timeStamp = TimeStamp(self.invariant)</div><div>         cat = self._timeStamp.t</div><div>diff -ru ../venv3.9/lib/python3.9/site-packages/reportlab/pdfgen/canvas.py reportlab/pdfgen/canvas.py</div><div>--- ../venv3.9/lib/python3.9/site-packages/reportlab/pdfgen/canvas.py<span style="white-space:pre-wrap">     </span>2025-06-27 12:37:23.523252635 -0400</div><div>+++ reportlab/pdfgen/canvas.py<span style="white-space:pre-wrap">        </span>2025-06-29 15:08:52.786088514 -0400</div><div>@@ -1131,9 +1131,9 @@</div><div>         """</div><div>         #check if we've done this one already...</div><div>         if isUnicode(command):</div><div>-            rawName = 'PS' + hashlib.md5(command.encode('utf-8')).hexdigest()</div><div>+            rawName = 'PS' + hashlib.md5(command.encode('utf-8'),usedforsecurity=False).hexdigest()</div><div>         else:</div><div>-            rawName = 'PS' + hashlib.md5(command).hexdigest()</div><div>+            rawName = 'PS' + hashlib.md5(command,usedforsecurity=False).hexdigest()</div><div>         regName = self._doc.getXObjectName(rawName)</div><div>         psObj = self._doc.idToObject.get(regName, None)</div><div>         if not psObj:</div></div></div><div><br></div></div>_______________________________________________<br>
reportlab-users mailing list<br>
<a href="mailto:reportlab-users@lists2.reportlab.com" target="_blank">reportlab-users@lists2.reportlab.com</a><br>
<a href="https://pairlist2.pair.net/mailman/listinfo/reportlab-users" rel="noreferrer" target="_blank">https://pairlist2.pair.net/mailman/listinfo/reportlab-users</a><br>
</blockquote></div><div><br clear="all"></div><div><br></div></div>