Hi all,
I have only recently started working with NIM and I am loving it but I have hit a little stumbling block. I am trying to get some simple AES encryption working and for the life of me I can't seem to be able to do it. I have no doubt some AES experts on here will spot my mistake instantly.
Any chance you could have a go at running this code to see why I can't decrypt the input please? The important bits are:
I have added some echo outputs to see what I think should be coming out the other side but I never seem to be able to even get the hex version of my name visible! I am using byte sequences to store the IV and Key as in my eventual code that is what I will need to use. Thanks Here is the basic code:
import winim
import nimcrypto
import std/base64
## Converts a string to the corresponding byte sequence.
proc toByteSeq*(str: string): seq[byte] {.inline.} =
@(str.toOpenArrayByte(0, str.high))
func toString*(bytes: openArray[byte]): string {.inline.} =
## Converts a byte sequence to the corresponding string.
let length = bytes.len
if length > 0:
result = newString(length)
copyMem(result.cstring, bytes[0].unsafeAddr, length)
proc decrypt(encryptedInput: string, key: seq[byte], iv: seq[byte]): seq[byte] =
# some var definitions for decrypting
let cryptedInput = toByteSeq(encryptedInput)
var
encodedPay = newSeq[byte](len(cryptedInput))
decodedPay = newSeq[byte](len(cryptedInput))
encodedPay = cryptedInput
# Decrypt the encrypted bytes of the main payload
var test: CBC[aes128]
test.init(key, iv)
test.decrypt(encodedPay, decodedPay)
test.clear()
echo "IV : ", toHex(iv)
echo "PLAIN TEXT HEX : ", toHex(encodedPay)
#echo "PLAIN TEXT: ", plaintext
echo "DECODED TEXT HEX: ", toHex(decodedPay)
echo "SHOULD BE HEX : 64617669646b656e6e656479" #Decrypted AES CBC128 of davidkennedy
echo "DECODED TEXT B64: ", encode(decodedPay)
echo "SHOULD BE B64 : ZGF2aWRrZW5uZWR5"
#echo "DECODED TEXT: ", decText
echo "String :", decodedPay.toString() # convert tranformed text to string
when isMainModule:
let key: seq[byte] = toByteSeq("KEYDEFGHIHKLOMNA")
let iv: seq[byte] = toByteSeq("IVCDEFGHIHKLOMNA")
var encryptedInput = "D9C69ED09EACB9A874FCBEB3DC054522" #AES CBC128 encryption of davidkennedy in HEX
var code = decrypt(encryptedInput,key,iv)
First, you should never be using CBC mode for a number of reasons, including no tamper detection, and potential issues with padding attacks, such as ones that have hit TLS in the past.
Instead, you should be using a more modern encryption mode that does integrity checking by default.
NimCrypto isn't something I'd recommend; its maintainers don't seem to have too deep an understanding of cryptography (or perhaps don't care much about people building secure systems on top of their library). They only support one modern mode, and they implement it incorrectly. They seem not to be in a hurry to fix this.
Meanwhile, Nim ships working with OpenSSL out of the box. OpenSSL has GCM built in, which is the default symmetric cipher suite for AES. On most platforms, OpenSSL will use hardware accelerated code for this. However, Nim's standard libraries don't expose it directly.
I've wrapped some of the AES bits in OpenSSL for my own needs, which you're welcome to use if you like: https://github.com/crashappsec/nimutils
I've not tried to make it ready for anyone else, so there's no documentation at this point, but here's some sample code:
import nimutils, strutils, options
var
encCtx: GcmCtx
decCtx: GcmCtx
nonce: string
ct: string
pt = "This is a test between disco and death"
key = "0123456789abcdef"
gcmInitEncrypt(encCtx, key)
gcmInitDecrypt(decCtx, key)
echo "Initial pt: ", pt
for i in 1 .. 10:
ct = encCtx.gcmEncrypt(pt)
nonce = encCtx.gcmGetNonce()
pt = decCtx.gcmDecrypt(ct, nonce).get("<error>")
echo "Nonce: ", nonce.hex()
echo "CT: "
echo ct.strDump()
echo "Decrypted: ", pt
Alternately, I believe someone in the community maintains a good wrapping of libSodium; the SecretBox abstraction there. Thanks jtv, that is really useful code and very much appreciated. Definitely going to incorporate that into my future projects.
One catch for me is the tool I am using only spits out AES CBC 128 given it is only worried about some minor obsfucation so I still need to somehow crack my issue. If you have any ideas I am all ears. Thanks again
Regarding your code, your problem is that your input is hex-encoded, but you decode it as bytes.
A simple fix would be replacing this line:
let cryptedInput = toByteSeq(encryptedInput)
... with this:
let cryptedInput = cast[seq[byte]](parseHexStr(encryptedInput))
and importing std/strutils for the parseHexStr. Mind you that casting is generally frowned upon, but so is copying memory from unsafeAddrs like you seem to be already doing.
Thank you that worked a treat and I appreciate I will definitely need to improve my best practices with this code. Just dipping my toe in at the moment.
One thing I noticed is that it seems to be padded with extra characters compared to what I think it should be in HEX and a String output. Any idea how to avoid this? Thanks again.
DECODED TEXT HEX : 64617669646B656E6E65647904040404 SHOULD BE HEX : 64617669646b656e6e656479 DECODED TEXT B64 : ZGF2aWRrZW5uZWR5BAQEBA== SHOULD BE B64 : ZGF2aWRrZW5uZWR5 String :davidkennedy
If I could trouble you again dissolved_girl I think what we spoke about above is what is causing me trouble today. I have started writing out the Non Encrypted Raw file then adding the Decrypted Raw data underneath and I can see after 16 entries the data is padded out with 16 entries of 16 then it continues correctly.
This padding out of 16s is stopping my code from running the decrypted version properly. I am reading in 14MB files of encrypted data so the below is just an excerpt but my original code above is what I am using. Is there a way to amend my original code to prevent the decrypted code from padding this data? I realise this could be a very newbie question.....Thank you
Non Encrypted@[252, 72, 131, 228, 240, 232, 200, 0, 0, 0, 65, 81, 65, 80, 82, 81, 86, 72, 49, 210, 101, 72, 139, 82, 96, 72, 139, 82
The Decrypted @[252, 72, 131, 228, 240, 232, 200, 0, 0, 0, 65, 81, 65, 80, 82, 81, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 86, 72, 49, 210, 101, 72, 139, 82, 96, 72, 139, 82