private static String computeTruncatedHash(final String randomStr) {
        // Now get a sha2-256 hash of our random string and truncated to 10 chars.
        // This part acts as a checksum of the random code part and provides a means to verify that
        // something that looks like a stroom API key is actually one, e.g. for spotting stroom API keys
        // left in the clear. We may never use this checksum, but you never know.
        // See https://github.blog/2021-04-05-behind-githubs-new-authentication-token-formats/
        // for the idea behind this.
        final String sha256 = DigestUtils.sha256Hex(randomStr);
        return sha256.substring(0, TRUNCATED_HASH_LENGTH);
    }
Generate a random API key of the form
<pre>{@code sak_<truncated hash>_<random code>}</pre>
where:
<p>
{@code <hash>} is the SHA2-256 hash of {@code <random code>} and truncated to the first
ten characters. This acts as a checksum for the random code part so you can be confident that something
that looks like a stroom api key is actually one. As the prefix is stored in Stroom, it serves as a way
for the user to match a key they have with the API Key record in stroom (that doesn't hold the full key).
</p>
<p>
{@code <random code>} is a string of 128 crypto random characters using the Base58 character set.
</p>

<p>The following <a href="https://gchq.github.io/CyberChef/">cyberchef</a> recipe takes a full API key
as input and highlights the hash part if it is valid, thus indicating that the API key is a valid one.
</p>
<pre>

Register('sak_(.*)_.*$',true,false,false)
Regular_expression('User defined','sak_.*_(.*)$',true,true,false,false,false,false,'List capture groups')
SHA2('256',64,160)
Regular_expression('User defined','^.{10}',true,true,false,false,false,false,'List matches')
Regular_expression('User defined','^$R0$',true,true,false,false,false,false,'Highlight matches')

</pre>
stroom-security/stroom-security-impl/src/main/java/stroom/security/impl/apikey/ApiKeyGenerator.java