Verifying signature of payload in Go - go

I am verifying the identity of the sender of a piece of data. I am provided the RSA public key in a PEM format and I know the data is passed through the SHA256 hashing function. The equivalent verification on the node.js platform:
Ticket.prototype.verify = function (ticket) {
if (!ticket) return null;
var pubkey = fs.readFileSync('/etc/SCAMP/auth/ticket_verify_public_key.pem');
var parts = ticket.split(',');
if (parts[0] != '1') return null;
var sig = new Buffer(parts.pop().replace(/-/g,'+').replace(/_/g,'/'), 'base64');
var valid = crypto.createVerify('sha256').update( new Buffer(parts.join(',')) ).verify( pubkey, sig )
Which can verify:
1,3063,21,1438783424,660,1+20+31+32+34+35+36+37+38+39+40+41+42+43+44+46+47+48+50+53+56+59+60+61+62+67+68+69+70+71+75+76+80+81+82+86+87+88+102+104+105+107+109+110+122+124,PcFNyWjoz_iiVMgEe8I3IBfzSlUcqUGtsuN7536PTiBW7KDovIqCaSi_8nZWcj-j1dfbQRA8mftwYUWMhhZ4DD78-BH8MovNVucbmTmf2Wzbx9bsI-dmUADY5Q2ol4qDXG4YQJeyZ6f6F9s_1uxHTH456QcsfNxFWh18ygo5_DVmQQSXCHN7EXM5M-u2DSol9MSROeBolYnHZyE093LgQ2veWQREbrwg5Fcp2VZ6VqIC7yu6f_xYHEvU0-ZsSSRMAMUmhLNhmFM4KDjl8blVgC134z7XfCTDDjCDiynSL6b-D-
by splitting on the last ,. The left side of the split is the ticket data I care about, the right side is the signature which I need to verify before I can use the ticket data.
I have tried to port the logic to go:
func TestSigVerification(t *testing.T) {
block, _ := pem.Decode(signingPubKey)
if block == nil {
t.Errorf("expected to block to be non-nil CERTIFICATE", block)
}
key, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
t.Errorf("could not parse PKIXPublicKey: `%s`", key)
}
rsaPubKey, ok := key.(*rsa.PublicKey)
if !ok {
t.Errorf("couldn't cast to rsa.PublicKey!")
}
ticket,_ := ParseTicketBytes(fullTicketBytes)
h := sha256.New()
h.Write(ticketBytes)
digest := h.Sum(nil)
err = rsa.VerifyPKCS1v15(rsaPubKey, crypto.SHA256, digest, ticket.Signature)
if err != nil {
t.Errorf("could not verify ticket: `%s` (digest: `%v`)", err, digest )
}
}
But I'm pretty sure VerifyPKCS1v15 is not equivalent to node's crypto.createVerify and this test case fails. What should I be using? How can I use the public key to decrypt the signature and get the sha256? once I have the decrypted sha256 value I could just do a basic comparison with the sha256 I have generated.
Here's a runnable playground example: http://play.golang.org/p/COx2OG-AiA

Though I couldn't get it to work, I suspect the issue is that you'll need to convert the sig from base64 into bytes via the base64 encoding. See this example here:
http://play.golang.org/p/bzpD7Pa9mr (especially lines 23 to 28, where they have to encode the sig from bytes to base64 string to print it, then feed the byte version into the sig check, indicating that you have to use the byte version and not base64 string)
Which I stumbled across on this post:
Signing and decoding with RSA-SHA in GO
I've found that golang generally expects bytes everywhere in byte encoding. I tried to decode your sig string from base64 to bytes however, even after replacing the '-' with '+' and the '_' with '/' it still won't work, for reasons unknown to me:
http://play.golang.org/p/71IiV2z_t8
At the very least this seems to indicate that maybe your sig is bad? If it isn't valid base64? I think if you can find a way to solve this the rest should work!

I tried running your code and it doesn't look like your ticket is well formed. It's not long enough. Where did you get the value from?
Double check that ticket -- that may just be enough to get you going.

Related

How to decode hex to ASN.1 in golang

I have an ECDSA public key that that is returned to me from an HSM in ASN.1 DER format. I need to create a bitcoin compatible key 33 byte. When I print out key in hex.EncodeToString(pubkey) I get the following output:
3056301006072a8648ce3d020106052b8104000a034200049bb8e80670371f45508b5f8f59946a7c4dea4b3a23a036cf24c1f40993f4a1daad1716de8bd664ecb4596648d722a4685293de208c1d2da9361b9cba74c3d1ec
I use an online decoder here: https://holtstrom.com/michael/tools/asn1decoder.php
And it outputs:
0x049bb8e80670371f45508b5f8f59946a7c4dea4b3a23a036cf24c1f40993f4a1daad1716de8bd664ecb4596648d722a4685293de208c1d2da9361b9cba74c3d1ec
I can then take that and hex.DecodeString(str) which gives me the necessary format to input this into addrPubKey, err := btcutil.NewAddressPubKey(bs, &chaincfg.TestNet3Params).
How do I decode this in golang to get the 0x049... output?
Thanks
The first thing we need is to use the encoding/asn1 package from the standard library.
You only have to give go the right struct to decode into. From your link we can see that we have a SEQUENCE that contains another SEQUENCE with two OBJECTIDENTIFIER and a BITSTRING. In go this will be:
type Ids struct {
OBi1 asn1.ObjectIdentifier
OBi2 asn1.ObjectIdentifier
}
type PubKey struct {
Id Ids
Bs asn1.BitString
}
Now we only have to UnMarshall the data to this structure:
str := `3056301006072a8648ce3d020106052b8104000a034200049bb8e80670371f45508b5f8f59946a7c4dea4b3a23a036cf24c1f40993f4a1daad1716de8bd664ecb4596648d722a4685293de208c1d2da9361b9cba74c3d1ec`
bstring, err := hex.DecodeString(str)
if (err != nil) {
panic(err)
}
var decode PubKey
_, err = asn1.Unmarshal(bstring, &decode)
if (err != nil) {
panic(err)
}
fmt.Println(hex.EncodeToString(decode.Bs.Bytes))
Note that you don't have to encode the string to hex and back again, since Unmarshall accepts a byte array
This will print the expected result:
049bb8e80670371f45508b5f8f59946a7c4dea4b3a23a036cf24c1f40993f4a1daad1716de8bd664ecb4596648d722a4685293de208c1d2da9361b9cba74c3d1ec
Once again you probably don't need to encode to string.

How do I encrypt special characters or numbers using AES-256?

I would like to encrypt a string in Go using AES-256, without any GCM processing, to compare against MQL4. I encounter issues when I try to encrypt special characters or numbers. Should I be pre-processing my plaintext somehow? I am new to Go so any help would be appreciated; my code is below this explanation.
If I encrypt the plaintext "This is a secret" and then decrypt the ciphertext (encoded to hex), I get the same result (i.e. "This is a secret"). pt is the variable name of the plaintext in the code below.
If I try to encrypt "This is a secret; 1234", the ciphertext has a group of zeroes at the end, and when I decrypt I only get "This is a secret". Similar ciphertext in MQL4 does not have zeroes at the end and decrypts correctly.
If I try to encrypt only "1234", I get build errors, stemming from "crypto/aes.(*aesCipherAsm).Encrypt(0xc0000c43c0, 0xc0000ac058, 0x4, 0x4, 0xc0000ac070, 0x4, 0x8)
C:/Program Files/Go/src/crypto/aes/cipher_asm.go:60 +0x125"
Here is my code:
package main
import (
"crypto/aes"
"encoding/hex"
"fmt"
)
func main() {
// cipher key
key := "thisis32bitlongpassphraseimusing"
// plaintext
pt := "This is a secret"
// pt := "This is a secret; 1234" // zeroes in ciphertext
// pt := "1234" // doesn't build
c := EncryptAES([]byte(key), pt)
// plaintext
fmt.Println(pt)
// ciphertext
fmt.Println(c)
// decrypt
DecryptAES([]byte(key), c)
}
func EncryptAES(key []byte, plaintext string) string {
c, err := aes.NewCipher(key)
CheckError(err)
out := make([]byte, len(plaintext))
c.Encrypt(out, []byte(plaintext))
return hex.EncodeToString(out)
}
func DecryptAES(key []byte, ct string) {
ciphertext, _ := hex.DecodeString(ct)
c, err := aes.NewCipher(key)
CheckError(err)
pt := make([]byte, len(ciphertext))
c.Decrypt(pt, ciphertext)
s := string(pt[:])
fmt.Println("DECRYPTED:", s)
}
func CheckError(err error) {
if err != nil {
panic(err)
}
}
You're creating a raw AES encryptor here. AES can only encrypt precisely 16 bytes of plaintext, producing exactly 16 bytes of cipher text. Your first example "This is a secret" is exactly 16 bytes long, so it works as expected. Your second example is too long. Only the first 16 bytes are being encrypted. The third example is too short and you're likely running into uninitialized memory.
The specific characters in your text are irrelevant. Encryption is performed on raw bytes, not letters.
In order to encrypt larger (or smaller) blocks of text, you need to use a block cipher mode on top of AES. Common modes are GCM, CBC, and CTR, but there are many others. In most cases, when someone says "AES" without any qualifier, they mean AES-CBC. (GCM is becoming much more popular, and it's a great mode, but it's not so popular that it's assumed quite yet.)
I don't know anything about MQL4, but I assume you're trying to reimplement CryptEncode? I don't see any documentation on how they do the encryption. You need to know what mode they use, how they derive their key, how they generate (and possibly encode) their IV, whether they include an HMAC or other auth, and more. You need to know exactly how they implement whatever they mean by "CRYPT_AES256." There is no one, standard answer to this.
MQL4 only supports a very specific implementation of AES encryption and unless you use the correct settings in your other code you will not achieve compatibility between the two platforms.
Specifically you need to ensure the following are implemented:
Padding Mode: Zeros
Cipher Mode: ECB (so no IV)
KeySize: 256
BlockSize: 128
You also need to remember in MQL4 that encryption/decryption is a two stage process (to AES256 then to BASE64).
You can try the online AES encryption/decryption tool to verify your results available here: The online toolbox

Golang Base64 signature verification VS CryptoJS

I am trying to convert a Node.js script to Golang. However, I always get the error "error: illegal base64 data at input byte 40" while decoding Base64. May someone help me, already debug and read documentation for few hours... Help is needed!
// Node.js exampe (code copied from Facebook Instant game SDK example)
const CryptoJS = require('crypto-js');
var firstpart = signedRequest.split('.')[0];
firstpart = firstpart.replace(/-/g, '+').replace(/_/g, '/');
const signature = CryptoJS.enc.Base64.parse(firstpart).toString(); // <-- fail here
const dataHash = CryptoJS.HmacSHA256(signedRequest.split('.')[1], '<APP_SECRET>').toString();
var isValid = signature === dataHash;
const json = CryptoJS.enc.Base64.parse(signedRequest.split('.')[1]).toString(CryptoJS.enc.Utf8);
const data = JSON.parse(json);
Code above is the sample code from Facebook, and the following code (below) is written by me.
parts := strings.Split(signedRequest, ".")
firstPart := parts[0]
replaced := strings.Replace(firstPart, "-", "+", -1)
replaced = strings.Replace(replaced, "_", "/", -1)
signatureByte, err := base64.StdEncoding.DecodeString(replaced) // <-- ERROR here
if err != nil {
fmt.Println("error:", err)
return false, err
}
signature := string(signatureByte)
dataHash := createHmacSHA256(parts[1], "<APP_SECRET>") // TODO: not sure, to string or hex string?
isValid := signature == dataHash
if isValid {
return true, nil
}
The Go Playground is at here https://play.golang.org/p/ilSbqamFdV_-
First of all, get rid of the strings.Replaces and just use base64.URLEncoding instead of StdEncoding, since the URL alphabet is clearly what your data is in.
Also, standard base64 data is padded, but your data is not padded, so you need the "raw" encoding, i.e. base64.RawURLEncoding. This works:
firstPart := parts[0]
signatureByte, err := base64.RawURLEncoding.DecodeString(firstPart)
https://play.golang.org/p/Pj_LLfirU8M
See the base64 package docs and the base64 standard for more info.

Go AES CFB compatibility

I am developing a client-side app in Go that relies on AES CFB. The server-side is written in C. My problem is that Go's AES CFB implementation appears to differ from many others (including OpenSSL). I wrote this to test my theory:-
package main
import (
"fmt"
"encoding/hex"
"crypto/cipher"
"crypto/aes"
)
func encrypt_aes_cfb(plain, key, iv []byte) (encrypted []byte) {
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
encrypted = make([]byte, len(plain))
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(encrypted, plain)
return
}
func decrypt_aes_cfb(encrypted, key, iv []byte) (plain []byte) {
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
plain = make([]byte, len(encrypted))
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(plain, encrypted)
return
}
func main() {
plain := []byte("Hello world.....")
key := []byte("01234567890123456789012345678901")
iv := []byte("0123456789012345")
enc := encrypt_aes_cfb(plain, key, iv)
dec := decrypt_aes_cfb(enc, key, iv)
fmt.Println("Key: ", hex.EncodeToString(key))
fmt.Println("IV: ", hex.EncodeToString(iv))
fmt.Println("Enc: ", hex.EncodeToString(enc))
fmt.Println("In: ", hex.EncodeToString(plain))
fmt.Println("Out: ", hex.EncodeToString(dec))
}
When this is run, it appears to work perfectly, however, if the encrypted bytes are pasted into another AES implementation and decrypted using the same key and IV, the plaintext is corrupted (except for the first Byte). http://aes.online-domain-tools.com/ provides a simple means to test this. Any suggestions why this might be happening and how I can resolve it?
Thanks
Steve
(Firstly, an obligatory warning: CFB mode is a sign of homegrown crypto. Unless you're implementing OpenPGP you should be using an AE mode like AES-GCM or NaCl's secretbox. If you're forced to use CFB mode, I hope that you're authenticating ciphertexts with an HMAC at least.)
With that aside, CFB mode in Go is there for OpenPGP support. (OpenPGP uses both a tweaked CFB mode called OCFB, and standard CFB mode in different places.) The Go OpenPGP code appears to interoperate with other implementations at least.
Nick is correct that test vectors are missing in the Go crypto package. The testing was coming from the OpenPGP code, but packages should stand alone and so I'll add tests to crypto/cipher with the test vectors from section F.3.13 of [1].
My best guess for the source of any differences is that CFB is parameterised by a chunk size. This is generally a power of two number of bits up to the block size of the underlying cipher. If the chunk size isn't specified then it's generally taken to be the cipher block size, which is what the Go code does. See [1], section 6.3. A more friendly explanation is given by [2].
Small chunk sizes were used in the dark ages (the late 90s) when people worried about things like cipher resync in the face of ciphertext loss. If another implementation is using CFB1 or CFB8 then it'll be very different from Go's CFB mode and many others. (Go's code does not support smaller chunk sizes.)
[1] http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf
[2] http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29
I investigated this with the following inputs because I was unsure of the bit/byte order for both inputs and outputs :
Key: 00000000000000000000000000000000
IV: 00000000000000000000000000000000
Enc: 66
In: 00
Out: 00
http://play.golang.org/p/wl2y1EE6lK
Which matches the tool you provided, and then this :
Key: 00000000000000000000000000000000
IV: 00000000000000000000000000000000
Enc: 66e94b
In: 000000
Out: 000000
http://play.golang.org/p/DNC42m2oU5
Which doesn't match the tool :
6616f9
http://aes.online-domain-tools.com/link/63687gDNzymApefh/
The first byte matches, which indicates there may be a feedback issue.
So I checked the Go package's code and I think there is a bug here :
func (x *cfb) XORKeyStream(dst, src []byte) {
for len(src) > 0 {
if x.outUsed == len(x.out) {
x.b.Encrypt(x.out, x.next)
x.outUsed = 0
}
if x.decrypt {
// We can precompute a larger segment of the
// keystream on decryption. This will allow
// larger batches for xor, and we should be
// able to match CTR/OFB performance.
copy(x.next[x.outUsed:], src)
}
n := xorBytes(dst, src, x.out[x.outUsed:])
if !x.decrypt {
copy(x.next[x.outUsed:], dst) // BUG? `dst` should be `src`
}
dst = dst[n:]
src = src[n:]
x.outUsed += n
}
}
EDIT
After a second look at CFB mode it seems that Go's code is fine, so yeah it may be the other implementations are wrong.

CryptoJS.AES and Golang

i already managed to share a random symetric key via rsa.
However i fail to make aes-encryption work with it.
Problem seems to be the salt and initialization vector, that cryptoJS uses.
First it's output is along the lines of:
U2FsdGVkX19wbzVqqOr6U5dnuG34WyH+n1A4PX9Z+xBhY3bALGS7DOa/aphgnubc
Googling for the reoccuring "U2FsdGVkX" or "cryptoJS.AES output" sadly is of no use.
On the other hand, golang's aes requires only a 32bit key and input of 32bit length each.
Which means i have to somehow split the above into the corresponding blocks and figure out, how to create the 32bit key out of the secret key and the data above (which proably includes salt + init vector).
Sadly neither http://code.google.com/p/crypto-js nor any google search provide me with a solution.
By the way - my encryption right now:
var arr = new Array(32);
symetricKey = "";
var symHex = "";
rng.nextBytes(arr);
for(var i = 0; i < arr.length; i++){
symetricKey += String.fromCharCode(arr[i]);
//symHex= arr[i].toString(16), added a 0 if needed (so length always increases by 2)
}
//submit symetric via rsa. This is working, the server gets that key
var enc = CryptoJS.AES.encrypt(unencrypted, symetricKey)
//submit enc, stuck now - what to do with it on the server?
Edit: After the Base64 response:
Thanks for the base64 input.
However i still don't manage to bring it to work.
Especially since the encoded string starts with "SALTED",
i believe, that there might be a problem.
Way i try to encode now:
Encoded on Client by:
var unencrypted = "{mail:test,password:test}"
var enc = CryptoJS.AES.encrypt(unencrypted, symKey)
On Server, the variables enc and symKey are the same as on Client:
baseReader := base64.NewDecoder(base64.StdEncoding, strings.NewReader(enc))
encData, err := ioutil.ReadAll(baseReader)
//if err != nil { ....}, doesn't happen here
cipher, err := aes.NewCipher(symKey)
//if err != nil { ....}, doesn't happen here
byteData := make([]byte, len(encData))
cipher.Decrypt(byteData, encData)
fmt.Println("Dec: ", string(byteData))
//Outputs unrepresentable characters
Any idea?
The output of CryptoJS.AES.encrypt is a CipherParams object containing the key, IV, optional salt, and ciphertext. The string you were referencing was a OpenSSL-compatible formatted string.
var encrypted = CryptoJS.AES.encrypt("Message", "Secret Passphrase");
alert(encrypted.key); // 74eb593087a982e2a6f5dded54ecd96d1fd0f3d44a58728cdcd40c55227522223
alert(encrypted.iv); // 7781157e2629b094f0e3dd48c4d786115
alert(encrypted.salt); // 7a25f9132ec6a8b34
alert(encrypted.ciphertext); // 73e54154a15d1beeb509d9e12f1e462a0
alert(encrypted); // U2FsdGVkX1+iX5Ey7GqLND5UFUoV0b7rUJ2eEvHkYqA= (OpenSSL-compatible format strategy)
CryptoJS' default encryption mode is CBC. You should pass the IV along with your symmetric key during your RSA-encrypted exchange. With the symmetric key, IV, and cipher text byte arrays on the server, you can decrypt it in Go similar to this:
c, err := aes.NewCipher(key)
cfbdec := cipher.NewCBCDecrypter(c, iv)
plaintext := make([]byte, len(ciphertext))
cfbdec.CryptBlock(plaintext, ciphertext)
U2FsdGVkX19wbzVqqOr6U5dnuG34WyH+n1A4PX9Z+xBhY3bALGS7DOa/aphgnubc
That data is base64 encoded, raw it looks something like this:
Salted__po5jSgm[!P8=Yacv,dj`
(note that there are unrepresentable characters in that string)
And this is exactly 32 bytes long.
Although, it don't directly but maybe you will find it useful to have a look at https://github.com/dgryski/dkeyczar.
Its Go implementation of KeyCzar, open source cryptographic toolkit originally developed by members of the Google Security Team
I hope you can learn something from this project
There are probably a few issues we'll need to work through.
First, you need to make sure you're supplying the correct type of arguments to encrypt(). It looks like you may have realized in a comment that you need to do CryptoJS.enc.Hex.parse(symetricKeyHex), though your original post doesn't reflect this yet.
Second, you need to decide how you're getting or creating the IV on both ends. CryptoJS can create one if you use a passphrase, but you're trying to use an actual key rather than a passphrase. That means you'll have to set the IV like you set the key.
Finally, you'll have to decide how you're going to transmit both the IV and the ciphertext. A typical and simple solution is to prepend the IV to the ciphertext, but really anything that's easily serializable/unserializable will work fine. Just make sure that whatever format you decide on is consistent on both ends.

Resources