AES-CFB from Go to JS forge

huangapple go评论91阅读模式
英文:

AES-CFB from Go to JS forge

问题

我正在尝试编写一个不安全的小程序。目的是对服务器端的mp3文件进行加密,以防止普通用户通过wget或"另存为"下载并使用。

服务器端的思路是加载mp3文件,使用AES-CFB加密,将密钥发送到头部,将加密后的mp3文件发送到响应体中。

服务器端使用Go的stdlib和AES-CFB加密。首先进行base64编码,然后直接输出加密后的[]byte。

客户端使用forge进行解密。发送一个xhr请求,读取arraybuffer,使用forge进行解密,并将输出写入控制台。

test.txt文件的内容为"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();
}

结果很奇怪。

它被"正确"解密,但每个解密结果都有一个随机长度的前缀或垃圾数据。

3个输出:

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

这里的输出被截断了,但与test.txt相同。正如你所看到的,它总是以随机垃圾数据为前缀。

我做错了什么?是forge对AES-CFB的实现有问题还是Go的实现有问题?为什么它们不兼容?为什么解密结果不同?如果AES-CFB是一个标准,为什么会有不同的实现?

我还尝试了gopherjs作为替代方案,效果很好,但是a)代码大小太大(约3.7MB),b)我不知道如何使用gopherjs播放解密后的音频。但这只是附带的问题。

英文:

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 (
	&quot;net/http&quot;
	&quot;io&quot;
	&quot;crypto/rand&quot;
	&quot;os&quot;
	&quot;crypto/aes&quot;
	&quot;crypto/cipher&quot;
	&quot;fmt&quot;
)

var (
	key = &quot;1234567890123456&quot;
	fn = &quot;test.txt&quot;
)

func main() {

	http.Handle(&quot;/file/&quot;, http.HandlerFunc(serveFile))
	http.Handle(&quot;/&quot;, http.FileServer(http.Dir(&quot;public&quot;)))
	http.ListenAndServe(&quot;:8080&quot;, 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(&quot;Access-Control-Allow-Origin&quot;, &quot;*&quot;)
	//w.Header().Set(&quot;Key&quot;, 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

&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;title&gt;MP3 Player Demo&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;button onclick=&quot;loadFile(&#39;1&#39;)&quot;&gt;Load&lt;/button&gt;

&lt;script src=&quot;node_modules/node-forge/js/forge.bundle.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;assets/reader.js&quot;&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;

reader.js

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

    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: &quot;3~&#230;&#255;K&#165;=&#174;œ&#170;&#255;&#194;&#223;this is just a test…&quot;, read: 0, _constructedStringLength: 69 }  reader.js:16:9
3~&#230;&#255;K&#165;=&#174;œ&#170;&#255;&#194;&#223;this is just a test and maybe it&#39;s working maybe not.  reader.js:17:9
--------------  reader.js:18:9
ArrayBuffer { byteLength: 69 }  reader.js:10:9
Object { data: &quot;&#197;’J&#199;9&#203;54&#171;&#218;ˆV&#171;this is just a test…&quot;, read: 0, _constructedStringLength: 69 }  reader.js:16:9
&#197;’J&#199;9&#203;54&#171;&#218;ˆV&#171;this is just a test and maybe it&#39;s working maybe not.  reader.js:17:9
--------------  reader.js:18:9
ArrayBuffer { byteLength: 69 }  reader.js:10:9
Object { data: &quot;&#170;&#213;x&#239;&#194;`zqA	\c&#253;’x#this is just a test…&quot;, read: 0, _constructedStringLength: 69 }  reader.js:16:9
&#170;&#213;x&#239;&#194;`zqA	\c&#253;’x#this is just a test and maybe it&#39;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.

答案1

得分: 0

你忘记移除初始化向量(IV)。这意味着 IV 也会被“解密”,导致出现无意义的结果。

这些无意义的结果似乎被解释为 UTF-8 编码,其中一个字符可能具有多字节的编码,所以你打印出来的 IV 的大小可能会有所不同。

因此,移除 IV,并在调试二进制值时尝试打印十六进制。

英文:

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.

huangapple
  • 本文由 发表于 2017年1月7日 20:55:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/41521671.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定