AES-CFB从Go到JS forge

I'm trying to write a little something that is not secure. The purpose is to encrypt server-side mp3s so they can't be just downloaded with wget or "save as" and used by the average Joe.

The idea is on the server side, load the mp3, aes-cfb encrypt it, send the key in the header, send the encrypted mp3 in the response body.

Server side is using Go's stdlib and AES-CFB encryption. As first with base64 encoding, then just plain output of the encrypted []byte-s.

Client side I'm using forge to decrypt. I send a xhr, read the arraybuffer, decrypt using forge and write output to the console.

The content of test.txt is "this is just a test and maybe it's working maybe not."

main.go

package main

import (
    "net/http"
    "io"
    "crypto/rand"
    "os"
    "crypto/aes"
    "crypto/cipher"
    "fmt"
)

var (
    key = "1234567890123456"
    fn = "test.txt"
)

func main() {

    http.Handle("/file/", http.HandlerFunc(serveFile))
    http.Handle("/", http.FileServer(http.Dir("public")))
    http.ListenAndServe(":8080", nil)
}

func serveFile(w http.ResponseWriter, r *http.Request) {
    file, e := os.Open(fn)
    if e != nil {
        fmt.Println(e.Error())
        return
    }
    defer file.Close()
    fi, _ := file.Stat()
    b := make([]byte, fi.Size())
    io.ReadFull(file, b)
    o := AESencrypt([]byte(key), b)
    w.Header().Set("Access-Control-Allow-Origin", "*")
    //w.Header().Set("Key", key)
    fmt.Println(o)
    fmt.Println(len(o))
    w.Write(o)
}

func AESencrypt(key []byte, content []byte) []byte {
    block, err := aes.NewCipher(key)
    if err != nil {
        panic(err)
    }
    ciphertext := make([]byte, aes.BlockSize + len(content))
    iv := ciphertext[:aes.BlockSize]
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        panic(err)
    }
    stream := cipher.NewCFBEncrypter(block, iv)
    stream.XORKeyStream(ciphertext[aes.BlockSize:], content)
    return ciphertext
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>MP3 Player Demo</title>
</head>
<body>
<button onclick="loadFile('1')">Load</button>

<script src="node_modules/node-forge/js/forge.bundle.js"></script>
<script src="assets/reader.js"></script>
</body>
</html>

reader.js

function loadFile(filename) {
    //var context = new AudioContext || new webkitAudioContext();
    var request = new XMLHttpRequest();
    var url = "http://localhost:8080/file/";
    request.open("GET", url + filename, true);
    request.responseType = "arraybuffer";
    request.onload = function () {
        var rt = request.response;
        console.log(rt);
        var decipher = forge.cipher.createDecipher('AES-CFB', forge.util.createBuffer('1234567890123456'));
        decipher.start({iv: forge.util.createBuffer('1234567890123456')});
        decipher.update(forge.util.createBuffer(rt));
        decipher.finish();
        console.log(decipher.output);
        console.log(decipher.output.bytes());
        console.log('--------------');
    };

    request.send();
}

The result is weird.

It is "properly" decrypted however there's a prefix or garbage of random length with each decrypted result.

3 outputs:

ArrayBuffer { byteLength: 69 }  reader.js:10:9
Object { data: "3~æÿK¥=®ªÿÂßthis is just a test…", read: 0, _constructedStringLength: 69 }  reader.js:16:9
3~æÿK¥=®ªÿÂßthis is just a test and maybe it's working maybe not.  reader.js:17:9
--------------  reader.js:18:9
ArrayBuffer { byteLength: 69 }  reader.js:10:9
Object { data: "ÅJÇ9Ë54«ÚV«this is just a test…", read: 0, _constructedStringLength: 69 }  reader.js:16:9
ÅJÇ9Ë54«ÚV«this is just a test and maybe it's working maybe not.  reader.js:17:9
--------------  reader.js:18:9
ArrayBuffer { byteLength: 69 }  reader.js:10:9
Object { data: "ªÕxïÂ`zqA   \cýx#this is just a test…", read: 0, _constructedStringLength: 69 }  reader.js:16:9
ªÕxïÂ`zqA   \cýx#this is just a test and maybe it's working maybe not.  reader.js:17:9
--------------  reader.js:18:9

The output here is truncated but it's the same is the test.txt . As you can see it's always prefixed with random garbage.

What am I doing wrong? Is the forge implementation of AES-CFB wrong or Go's? Why are they incompatible? Or why is it that the decryption differs? And if AES-CFB is a standard why are there different implementations?

I also tried gopherjs as an alternative and that works just fine but a) the code size is too big (~3.7MB) and b) I wouldn't know how to play back the decrypted audio using gopherjs. But that's just as an aside.

You're forgetting to remove the IV. This means the IV gets "decrypted" as well, resulting in nonsense.

The nonsense seems to be interpreted as UTF-8, where a character may have a multi-byte encoding, so the size of the IV you print out may differ somewhat.

So remove the IV, and try printing out hexadecimals when debugging binary values.