I wanted to have a PBKDF2-HMAC-SHA256 hashing proc in nim and use the implementation provided by openssl for that. This shall work with both openssl 1 and openssl 3.
As part of that process, I need to figure out the correct size the hash should have for the given hashing function. OpenSSL 3 provides those with a function called EVP_MD_get_size
.
If you only deal with openSSL3, you can just use:
from std/openssl import DLLSSLName, EVP_MD, EVP_sha256, DLLUtilName
proc EVP_MD_size_fixed*(md: EVP_MD): cint {.cdecl, dynlib: DLLUtilName, importc: "EVP_MD_get_size".}
That'll give you EVP_MD_get_size
renamed to a proc called EVP_MD_size_fixed
.
But if the openssl version is 1 and not 3 (e.g. because of debian), then the same proc is called "EVP_MD_size"
, not EVP_MD_get_size
. So you can't import it. You also can't just blindly import both EVP_MD_size
and EVP_MD_get_size
because it will throw a runtime error:
import std/[dynlib, openssl, strformat]
proc EVP_MD_size_fixed1*(md: EVP_MD): cint {.cdecl, dynlib: DLLUtilName, importc: "EVP_MD_get_size".}
proc EVP_MD_size_fixed2*(md: EVP_MD): cint {.cdecl, dynlib: DLLUtilName, importc: "EVP_MD_size".}
proc getOpenSSLMajorVersion(): uint =(getOpenSSLVersion() shr 28) and 0xF # Returns the major version of openssl
proc EVP_MD_size_fixed*(md: EVP_MD): cint =
assert md != nil
result =
if getOpenSSLMajorVersion() == 3:
EVP_MD_size_fixed1(md)
elif getOpenSSLMajorVersion() == 1:
EVP_MD_size_fixed2(md)
else:
raise newException(ValueError, fmt"This library supports only openssl 1 and 3. The openssl version we found was {getOpenSSLMajorVersion()}")
proc EVP_sha256_fixed(): EVP_MD {.cdecl, dynlib: DLLUtilName, importc: "EVP_sha256".}
let digestFunction = EVP_sha256_fixed()
let hashLength: cint = EVP_MD_size_fixed(digestFunction)
echo hashLength
On a system with openssl3 leads to: could not import: EVP_MD_size
Now how do I make it so I call EVP_MD_size
when the available openssl major version is 1 and EVP_MD_get_size
when the available openssl major version is 3?
The answer as provided by ElegantBeef and Yakoleb from the nim discord server is std/dynlib. It allows loading a library at runtime, loading procs from it and calling them.
import std/[dynlib, openssl, strformat]
type DigestSizeProc = proc(md: EVP_MD): cint {.cdecl.}
proc getOpenSSLMajorVersion(): uint =(getOpenSSLVersion() shr 28) and 0xF # Returns the major version of openssl
# Load lib
let lib = loadLibPattern(DLLUtilName)
assert lib != nil, fmt"Could not find lib {DLLUtilName}"
# Load proc <-- HERE IS THE MAGIC
let evp_md_size_getter: DigestSizeProc =
if getOpenSSLMajorVersion() == 3:
cast[DigestSizeProc](lib.symAddr("EVP_MD_get_size"))
elif getOpenSSLMajorVersion() == 1:
cast[DigestSizeProc](lib.symAddr("EVP_MD_size"))
else:
raise newException(ValueError, fmt"This library supports only openssl 1 and 3. The openssl version we found was {getOpenSSLMajorVersion()}")
assert evp_md_size_fixed != nil, fmt"Failed to load EVP_MD_size or EVP_MD_get_size proc from openssl version {getOpenSSLMajorVersion()}"
unloadLib(lib)
# Use proc
proc EVP_MD_size_fixed*(md: EVP_MD): cint =
assert md != nil
result = evp_md_size_getter(md)
proc EVP_sha256_fixed(): EVP_MD {.cdecl, dynlib: DLLUtilName, importc: "EVP_sha256".}
let digestFunction: EVP_MD = EVP_sha256_fixed()
let hashLength: cint = EVP_MD_size_fixed(digestFunction)
echo hashLength
As per the example there from std/dynlib, you can load a library using loadLib
. In our case, we're using std/openssl.DLLUtilName
as defined in the source-code there, so that'll return you a pattern of libcrypto.so(.3|.1.1|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|.48|.47|.46|.45|.44|.43|.41|.39|.38|.10|)
. That one requires loadLibPattern
though, as this is not just a name of single library but a library-pattern.
We first need to know which name the proc has and which type that proc is. In this case, the name is either EVP_MD_size
or EVP_MD_get_size
. With that, we can use lib.symAddr("<Symbol string>")
to get a pointer to the function. To be able to call it, we need to have nim interpret that pointer as a callable proc. So we'll have to cast it, but to which type?
We know the general type of that pointer, it's proc(md: EVP_MD): cint
(As you can see from the openssl docs where its defined int EVP_MD_get_size(const EVP_MD *md);
However, we also need to tell nim how to call the proc from openssl, the calling-convention . The one to use here is cdecl, so the final type to cast that pointer to is
type DigestSizeProc = proc(md: EVP_MD): cint {.cdecl.}
Once we know all of that, we can just do a variable assignment as in the Load proc
section.
Make sure to add asserts behind all the loading of libs/pointers to make sure you can easily figure out where Nil-pointer-access may occur.