mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-10-06 04:56:49 +08:00
feat: add crypto
This commit is contained in:
@@ -65,3 +65,15 @@ snap:
|
||||
onpub:
|
||||
transform:
|
||||
.* : $0
|
||||
|
||||
crypto:
|
||||
enable: false
|
||||
isstatic: false
|
||||
algo: aes_ctr # 加密算法 支持 aes_ctr xor_c
|
||||
encryptlen: 1024
|
||||
secret:
|
||||
key: your key
|
||||
iv: your iv
|
||||
onpub:
|
||||
transform:
|
||||
.* : $0
|
@@ -7,7 +7,7 @@ import (
|
||||
"m7s.live/v5"
|
||||
_ "m7s.live/v5/plugin/cascade"
|
||||
|
||||
// _ "m7s.live/v5/plugin/crypto"
|
||||
_ "m7s.live/v5/plugin/crypto"
|
||||
_ "m7s.live/v5/plugin/debug"
|
||||
_ "m7s.live/v5/plugin/flv"
|
||||
_ "m7s.live/v5/plugin/gb28181"
|
||||
|
43
plugin/crypto/api.go
Normal file
43
plugin/crypto/api.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package plugin_crypto
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
cryptopkg "m7s.live/v5/plugin/crypto/pkg"
|
||||
)
|
||||
|
||||
func (p *CryptoPlugin) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// 设置 CORS 头
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
// 获取 stream 参数
|
||||
stream := r.URL.Query().Get("stream")
|
||||
if stream == "" {
|
||||
http.Error(w, "stream parameter is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
//判断 stream 是否存在
|
||||
if !p.Server.Streams.Has(stream) {
|
||||
http.Error(w, "stream not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
keyConf, err := cryptopkg.ValidateAndCreateKey(p.IsStatic, p.Algo, p.Secret.Key, p.Secret.Iv, stream)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// cryptor, err := method.GetCryptor(p.Algo, keyConf)
|
||||
// if err != nil {
|
||||
// http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
// return
|
||||
// }
|
||||
// w.Write([]byte(cryptor.GetKey()))
|
||||
|
||||
w.Write([]byte(fmt.Sprintf("%s.%s", base64.RawStdEncoding.EncodeToString([]byte(keyConf.Key)), base64.RawStdEncoding.EncodeToString([]byte(keyConf.Iv)))))
|
||||
}
|
44
plugin/crypto/index.go
Normal file
44
plugin/crypto/index.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package plugin_crypto
|
||||
|
||||
import (
|
||||
m7s "m7s.live/v5"
|
||||
crypto "m7s.live/v5/plugin/crypto/pkg"
|
||||
)
|
||||
|
||||
var _ = m7s.InstallPlugin[CryptoPlugin](crypto.NewTransform)
|
||||
|
||||
type CryptoPlugin struct {
|
||||
m7s.Plugin
|
||||
IsStatic bool `desc:"是否静态密钥" default:"false"`
|
||||
Algo string `desc:"加密算法" default:"aes_ctr"` //加密算法
|
||||
EncryptLen int `desc:"加密字节长度" default:"1024"` //加密字节长度
|
||||
Secret struct {
|
||||
Key string `desc:"加密密钥" default:"your key"` //加密密钥
|
||||
Iv string `desc:"加密向量" default:"your iv"` //加密向量
|
||||
} `desc:"密钥配置"`
|
||||
}
|
||||
|
||||
// OnInit 初始化插件时的回调函数
|
||||
func (p *CryptoPlugin) OnInit() (err error) {
|
||||
// 初始化全局配置
|
||||
crypto.GlobalConfig = crypto.Config{
|
||||
IsStatic: p.IsStatic,
|
||||
Algo: p.Algo,
|
||||
EncryptLen: p.EncryptLen,
|
||||
Secret: struct {
|
||||
Key string `desc:"加密密钥" default:"your key"`
|
||||
Iv string `desc:"加密向量" default:"your iv"`
|
||||
}{
|
||||
Key: p.Secret.Key,
|
||||
Iv: p.Secret.Iv,
|
||||
},
|
||||
}
|
||||
|
||||
p.Info("crypto config initialized",
|
||||
"algo", p.Algo,
|
||||
"isStatic", p.IsStatic,
|
||||
"encryptLen", p.EncryptLen,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
31
plugin/crypto/pkg/getkey_test.go
Executable file
31
plugin/crypto/pkg/getkey_test.go
Executable file
@@ -0,0 +1,31 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetKey(t *testing.T) {
|
||||
stream := "/hdl/live/test0.flv"
|
||||
host := "http://localhost:8080/crypto/?stream="
|
||||
|
||||
r, err := http.DefaultClient.Get(host + stream)
|
||||
if err != nil {
|
||||
t.Error("get", err)
|
||||
return
|
||||
}
|
||||
b, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Error("read", err)
|
||||
return
|
||||
}
|
||||
b64 := strings.Split(string(b), ".")
|
||||
|
||||
key, err := base64.RawStdEncoding.DecodeString(b64[0])
|
||||
t.Log("key", key, err)
|
||||
iv, err := base64.RawStdEncoding.DecodeString(b64[1])
|
||||
t.Log("iv", iv, err)
|
||||
}
|
99
plugin/crypto/pkg/method/aes_cbc.go
Executable file
99
plugin/crypto/pkg/method/aes_cbc.go
Executable file
@@ -0,0 +1,99 @@
|
||||
package method
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
)
|
||||
|
||||
//加密过程:
|
||||
// 1、处理数据,对数据进行填充,采用PKCS7(当密钥长度不够时,缺几位补几个几)的方式。
|
||||
// 2、对数据进行加密,采用AES加密方法中CBC加密模式
|
||||
// 3、对得到的加密数据,进行base64加密,得到字符串
|
||||
// 解密过程相反
|
||||
|
||||
// AesEncrypt 加密 cbc 模式
|
||||
type AesCryptor struct {
|
||||
key []byte
|
||||
}
|
||||
|
||||
func newAesCbc(cfg Key) (ICryptor, error) {
|
||||
var cryptor *AesCryptor
|
||||
if cfg.Key == "" {
|
||||
return nil, errors.New("aes cryptor config no key")
|
||||
} else {
|
||||
cryptor = &AesCryptor{key: []byte(cfg.Key)}
|
||||
}
|
||||
return cryptor, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterCryptor("aes_cbc", newAesCbc)
|
||||
}
|
||||
|
||||
func (c *AesCryptor) Encrypt(origin []byte) ([]byte, error) {
|
||||
//创建加密实例
|
||||
block, err := aes.NewCipher(c.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//判断加密快的大小
|
||||
blockSize := block.BlockSize()
|
||||
//填充
|
||||
encryptBytes := pkcs7Padding(origin, blockSize)
|
||||
//初始化加密数据接收切片
|
||||
crypted := make([]byte, len(encryptBytes))
|
||||
//使用cbc加密模式
|
||||
blockMode := cipher.NewCBCEncrypter(block, c.key[:blockSize])
|
||||
//执行加密
|
||||
blockMode.CryptBlocks(crypted, encryptBytes)
|
||||
return crypted, nil
|
||||
}
|
||||
|
||||
func (c *AesCryptor) Decrypt(encrypted []byte) ([]byte, error) {
|
||||
//创建实例
|
||||
block, err := aes.NewCipher(c.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//获取块的大小
|
||||
blockSize := block.BlockSize()
|
||||
//使用cbc
|
||||
blockMode := cipher.NewCBCDecrypter(block, c.key[:blockSize])
|
||||
//初始化解密数据接收切片
|
||||
crypted := make([]byte, len(encrypted))
|
||||
//执行解密
|
||||
blockMode.CryptBlocks(crypted, encrypted)
|
||||
//去除填充
|
||||
crypted, err = pkcs7UnPadding(crypted)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return crypted, nil
|
||||
}
|
||||
|
||||
func (c *AesCryptor) GetKey() string {
|
||||
return base64.RawStdEncoding.EncodeToString(c.key)
|
||||
}
|
||||
|
||||
// pkcs7Padding 填充
|
||||
func pkcs7Padding(data []byte, blockSize int) []byte {
|
||||
//判断缺少几位长度。最少1,最多 blockSize
|
||||
padding := blockSize - len(data)%blockSize
|
||||
//补足位数。把切片[]byte{byte(padding)}复制padding个
|
||||
padText := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(data, padText...)
|
||||
}
|
||||
|
||||
// pkcs7UnPadding 填充的反向操作
|
||||
func pkcs7UnPadding(data []byte) ([]byte, error) {
|
||||
length := len(data)
|
||||
if length == 0 {
|
||||
return nil, errors.New("加密字符串错误!")
|
||||
}
|
||||
//获取填充的个数
|
||||
unPadding := int(data[length-1])
|
||||
return data[:(length - unPadding)], nil
|
||||
}
|
61
plugin/crypto/pkg/method/aes_ctr.go
Executable file
61
plugin/crypto/pkg/method/aes_ctr.go
Executable file
@@ -0,0 +1,61 @@
|
||||
package method
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type AesCtrCryptor struct {
|
||||
key []byte
|
||||
iv []byte
|
||||
}
|
||||
|
||||
func newAesCtr(cfg Key) (ICryptor, error) {
|
||||
var cryptor *AesCtrCryptor
|
||||
if cfg.Key == "" || cfg.Iv == "" {
|
||||
return nil, errors.New("aes ctr cryptor config no key")
|
||||
}
|
||||
cryptor = &AesCtrCryptor{key: []byte(cfg.Key), iv: []byte(cfg.Iv)}
|
||||
|
||||
return cryptor, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterCryptor("aes_ctr", newAesCtr)
|
||||
}
|
||||
|
||||
func (c *AesCtrCryptor) Encrypt(origin []byte) ([]byte, error) {
|
||||
|
||||
block, err := aes.NewCipher(c.key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
aesCtr := cipher.NewCTR(block, c.iv)
|
||||
|
||||
// EncryptRaw the plaintext
|
||||
ciphertext := make([]byte, len(origin))
|
||||
aesCtr.XORKeyStream(ciphertext, origin)
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
func (c *AesCtrCryptor) Decrypt(encrypted []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(c.key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
aesCtr := cipher.NewCTR(block, c.iv)
|
||||
|
||||
// Decrypt the ciphertext
|
||||
plaintext := make([]byte, len(encrypted))
|
||||
aesCtr.XORKeyStream(plaintext, encrypted)
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
func (c *AesCtrCryptor) GetKey() string {
|
||||
return fmt.Sprintf("%s.%s", base64.RawStdEncoding.EncodeToString(c.key), base64.RawStdEncoding.EncodeToString(c.iv))
|
||||
}
|
28
plugin/crypto/pkg/method/aes_ctr.java
Executable file
28
plugin/crypto/pkg/method/aes_ctr.java
Executable file
@@ -0,0 +1,28 @@
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class Aes256Ctr {
|
||||
|
||||
public static byte[] decrypt(byte[] ciphertext, byte[] key, byte[] iv) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
|
||||
return cipher.doFinal(ciphertext);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
int[] intArray = {253, 72, 209, 96, 36};
|
||||
byte[] ciphertext = new byte[intArray.length];
|
||||
for (int i = 0; i < intArray.length; i++) {
|
||||
ciphertext[i] = (byte) intArray[i];
|
||||
}
|
||||
// byte[] ciphertext = //byte[]{253,72,209,96,36]; // ciphertext to be decrypted
|
||||
byte[] key = "01234567012345670123456701234567".getBytes();// 256-bit key
|
||||
byte[] iv = "0123456701234567".getBytes();// initialization vector
|
||||
byte[] plaintext = decrypt(ciphertext, key, iv);
|
||||
System.out.println(new String(plaintext, "UTF-8"));
|
||||
}
|
||||
}
|
12
plugin/crypto/pkg/method/aes_ctr.js
Executable file
12
plugin/crypto/pkg/method/aes_ctr.js
Executable file
@@ -0,0 +1,12 @@
|
||||
const crypto = require('crypto');
|
||||
|
||||
const key = Buffer.from('01234567012345670123456701234567', 'utf8');
|
||||
console.log(key)
|
||||
const nonce = Buffer.from('0123456701234567', 'utf8');
|
||||
console.log(nonce)
|
||||
const ciphertext = Buffer.from([253,72,209,96,36]);
|
||||
|
||||
const decipher = crypto.createDecipheriv('aes-256-ctr', key, nonce);
|
||||
const plaintext = decipher.update(ciphertext);
|
||||
const finalPlaintext = decipher.final();
|
||||
console.log(Buffer.concat([plaintext, finalPlaintext]).toString());
|
14
plugin/crypto/pkg/method/aes_ctr_browser.js
Executable file
14
plugin/crypto/pkg/method/aes_ctr_browser.js
Executable file
@@ -0,0 +1,14 @@
|
||||
|
||||
var aesjs = require('aes-js');
|
||||
let ciphertext = Uint8Array.from([253, 72, 209, 96, 36]);
|
||||
|
||||
let key = aesjs.utils.utf8.toBytes('01234567012345670123456701234567');
|
||||
console.log(key)
|
||||
|
||||
let iv = aesjs.utils.utf8.toBytes('0123456701234567');
|
||||
console.log(iv)
|
||||
|
||||
var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(iv));
|
||||
var decryptedBytes = aesCtr.decrypt(ciphertext);
|
||||
console.log(decryptedBytes)
|
||||
console.log(aesjs.utils.utf8.fromBytes(decryptedBytes))
|
13
plugin/crypto/pkg/method/aes_ctr_node.js
Executable file
13
plugin/crypto/pkg/method/aes_ctr_node.js
Executable file
@@ -0,0 +1,13 @@
|
||||
const crypto = require('crypto');
|
||||
|
||||
let key = Buffer.from('01234567012345670123456701234567', 'utf8');
|
||||
console.log(key)
|
||||
let iv = Buffer.from('0123456701234567', 'utf8');
|
||||
console.log(iv)
|
||||
let ciphertext = Buffer.from([253,72,209,96,36]);
|
||||
|
||||
const decipher = crypto.createDecipheriv('aes-256-ctr', key, iv);
|
||||
const plaintext = decipher.update(ciphertext);
|
||||
const finalPlaintext = decipher.final();
|
||||
console.log(Buffer.concat([plaintext, finalPlaintext]).toString());
|
||||
|
92
plugin/crypto/pkg/method/cryptor_test.go
Executable file
92
plugin/crypto/pkg/method/cryptor_test.go
Executable file
@@ -0,0 +1,92 @@
|
||||
package method
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStream(t *testing.T) {
|
||||
encKey, _ := CreateKey(32)
|
||||
macKey, _ := CreateKey(32)
|
||||
|
||||
plaintext := "0123456789012345"
|
||||
pt := []byte(plaintext)
|
||||
var cfg Key
|
||||
cfg.EncKey = string(encKey)
|
||||
cfg.MacKey = string(macKey)
|
||||
c, _ := GetCryptor("stream", cfg)
|
||||
t.Log("key", c.GetKey())
|
||||
encryptData, err := c.Encrypt(pt)
|
||||
t.Log("stream encrypt base64", base64.RawStdEncoding.EncodeToString(encryptData), err)
|
||||
decryptData, err := c.Decrypt(encryptData)
|
||||
t.Log("stream decrypt", string(decryptData), err)
|
||||
if string(decryptData) != plaintext {
|
||||
t.Error("decrypt error")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAesCbc(t *testing.T) {
|
||||
|
||||
encKey, _ := CreateKey(16)
|
||||
|
||||
plaintext := "0123456789012345"
|
||||
pt := []byte(plaintext)
|
||||
|
||||
var cfg Key
|
||||
cfg.Key = string(encKey)
|
||||
c, _ := GetCryptor("aes_cbc", cfg)
|
||||
t.Log(c.GetKey())
|
||||
encryptData, err := c.Encrypt(pt)
|
||||
t.Log("aes_cbc encrypt base64", base64.RawStdEncoding.EncodeToString(encryptData), err)
|
||||
decryptData, err := c.Decrypt(encryptData)
|
||||
t.Log("aes_cbc decrypt", string(decryptData), err)
|
||||
|
||||
if string(decryptData) != plaintext {
|
||||
t.Error("decrypt error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAesCtr(t *testing.T) {
|
||||
|
||||
encKey, _ := CreateKey(32)
|
||||
iv, _ := CreateKey(16)
|
||||
plaintext := "0123456789012345"
|
||||
pt := []byte(plaintext)
|
||||
var cfg Key
|
||||
cfg.Key = string(encKey)
|
||||
cfg.Iv = string(iv)
|
||||
|
||||
c, _ := GetCryptor("aes_ctr", cfg)
|
||||
t.Log(c.GetKey())
|
||||
encryptData, err := c.Encrypt(pt)
|
||||
t.Log("aes_ctr encrypt ", string(encryptData), err)
|
||||
decryptData, err := c.Decrypt(encryptData)
|
||||
t.Log("aes_ctr decrypt", string(decryptData), err)
|
||||
|
||||
if string(decryptData) != plaintext {
|
||||
t.Error("decrypt error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestXor(t *testing.T) {
|
||||
|
||||
encKey, _ := CreateKey(32)
|
||||
iv, _ := CreateKey(16)
|
||||
plaintext := "0123456789012345"
|
||||
pt := []byte(plaintext)
|
||||
var cfg Key
|
||||
cfg.Key = string(encKey)
|
||||
cfg.Iv = string(iv)
|
||||
|
||||
c, _ := GetCryptor("xor", cfg)
|
||||
t.Log(c.GetKey())
|
||||
encryptData, err := c.Encrypt(pt)
|
||||
t.Log("xor encrypt ", string(encryptData), "len", len(string(encryptData)), err)
|
||||
decryptData, err := c.Decrypt(encryptData)
|
||||
t.Log("xor decrypt", string(decryptData), err)
|
||||
|
||||
if string(decryptData) != plaintext {
|
||||
t.Error("decrypt error")
|
||||
}
|
||||
}
|
51
plugin/crypto/pkg/method/icrypto.go
Executable file
51
plugin/crypto/pkg/method/icrypto.go
Executable file
@@ -0,0 +1,51 @@
|
||||
package method
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type ICryptor interface {
|
||||
Encrypt(origin []byte) ([]byte, error)
|
||||
Decrypt(encrypted []byte) ([]byte, error)
|
||||
GetKey() string // 获取密钥 格式:base64(key).base64(iv)
|
||||
}
|
||||
|
||||
const (
|
||||
CryptoEncrypt = iota + 1
|
||||
CryptoDecrypt
|
||||
)
|
||||
|
||||
type CryptoBuilder func(cfg Key) (ICryptor, error)
|
||||
|
||||
var (
|
||||
builders = make(map[string]CryptoBuilder)
|
||||
)
|
||||
|
||||
func RegisterCryptor(name string, builder CryptoBuilder) {
|
||||
builders[name] = builder
|
||||
}
|
||||
|
||||
func GetCryptor(cryptor string, cfg Key) (ICryptor, error) {
|
||||
builder, exists := builders[cryptor]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("Unknown ICryptor %q", cryptor)
|
||||
}
|
||||
return builder(cfg)
|
||||
}
|
||||
|
||||
func CreateKey(keySize int) ([]byte, error) {
|
||||
key := make([]byte, keySize)
|
||||
_, err := rand.Read(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func Md5Sum(s string) string {
|
||||
ret := md5.Sum([]byte(s))
|
||||
return hex.EncodeToString(ret[:])
|
||||
}
|
6
plugin/crypto/pkg/method/package.json
Executable file
6
plugin/crypto/pkg/method/package.json
Executable file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"aes-js": "^3.1.2",
|
||||
"crypto-js": "^4.1.1"
|
||||
}
|
||||
}
|
184
plugin/crypto/pkg/method/stream.go
Executable file
184
plugin/crypto/pkg/method/stream.go
Executable file
@@ -0,0 +1,184 @@
|
||||
package method
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Key struct {
|
||||
Key string
|
||||
Iv string
|
||||
EncKey string
|
||||
MacKey string
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterCryptor("stream", newStream)
|
||||
}
|
||||
|
||||
type StreamCryptor struct {
|
||||
enckey []byte
|
||||
macKey []byte
|
||||
encrypter *StreamEncrypter `yaml:"-"`
|
||||
decrypter *StreamDecrypter `json:"-"`
|
||||
}
|
||||
|
||||
func NewStreamEncrypter(encKey, macKey []byte) (*StreamEncrypter, error) {
|
||||
block, err := aes.NewCipher(encKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
iv := make([]byte, block.BlockSize())
|
||||
_, err = rand.Read(iv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
mac := hmac.New(sha256.New, macKey)
|
||||
|
||||
return &StreamEncrypter{
|
||||
Block: block,
|
||||
Stream: stream,
|
||||
Mac: mac,
|
||||
IV: iv,
|
||||
}, nil
|
||||
}
|
||||
func NewStreamDecrypter(encKey, macKey []byte, meta StreamMeta) (*StreamDecrypter, error) {
|
||||
block, err := aes.NewCipher(encKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stream := cipher.NewCTR(block, meta.IV)
|
||||
mac := hmac.New(sha256.New, macKey)
|
||||
|
||||
return &StreamDecrypter{
|
||||
Block: block,
|
||||
Stream: stream,
|
||||
Mac: mac,
|
||||
Meta: meta,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type StreamMeta struct {
|
||||
// IV is the initial value for the crypto function
|
||||
IV []byte
|
||||
// Hash is the sha256 hmac of the stream
|
||||
Hash []byte
|
||||
}
|
||||
|
||||
type StreamEncrypter struct {
|
||||
Source io.Reader
|
||||
Block cipher.Block
|
||||
Stream cipher.Stream
|
||||
Mac hash.Hash
|
||||
IV []byte
|
||||
}
|
||||
|
||||
// StreamDecrypter is a decrypter for a stream of data with authentication
|
||||
type StreamDecrypter struct {
|
||||
Source io.Reader
|
||||
Block cipher.Block
|
||||
Stream cipher.Stream
|
||||
Mac hash.Hash
|
||||
Meta StreamMeta
|
||||
}
|
||||
|
||||
// Read encrypts the bytes of the inner reader and places them into p
|
||||
func (s *StreamEncrypter) Read(p []byte) (int, error) {
|
||||
n, readErr := s.Source.Read(p)
|
||||
if n > 0 {
|
||||
s.Stream.XORKeyStream(p[:n], p[:n])
|
||||
err := writeHash(s.Mac, p[:n])
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, readErr
|
||||
}
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
// Meta returns the encrypted stream metadata for use in decrypting. This should only be called after the stream is finished
|
||||
func (s *StreamEncrypter) Meta() StreamMeta {
|
||||
return StreamMeta{IV: s.IV, Hash: s.Mac.Sum(nil)}
|
||||
}
|
||||
|
||||
// Read reads bytes from the underlying reader and then decrypts them
|
||||
func (s *StreamDecrypter) Read(p []byte) (int, error) {
|
||||
n, readErr := s.Source.Read(p)
|
||||
if n > 0 {
|
||||
err := writeHash(s.Mac, p[:n])
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
s.Stream.XORKeyStream(p[:n], p[:n])
|
||||
return n, readErr
|
||||
}
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
func newStream(cfg Key) (ICryptor, error) {
|
||||
var cryptor *StreamCryptor
|
||||
if (cfg.EncKey == "") || (cfg.MacKey == "") {
|
||||
return nil, errors.New("stream cryptor config not enckey or mackey")
|
||||
} else {
|
||||
encKey := []byte(cfg.EncKey)
|
||||
macKey := []byte(cfg.MacKey)
|
||||
|
||||
encrypter, err := NewStreamEncrypter(encKey, macKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decrypter, err := NewStreamDecrypter(encKey, macKey, encrypter.Meta())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cryptor = &StreamCryptor{
|
||||
enckey: encKey,
|
||||
macKey: macKey,
|
||||
encrypter: encrypter,
|
||||
decrypter: decrypter,
|
||||
}
|
||||
|
||||
}
|
||||
return cryptor, nil
|
||||
}
|
||||
|
||||
func (c *StreamCryptor) Encrypt(origin []byte) ([]byte, error) {
|
||||
c.encrypter.Source = bytes.NewReader(origin)
|
||||
return io.ReadAll(c.encrypter)
|
||||
}
|
||||
|
||||
func (c *StreamCryptor) Decrypt(encrypted []byte) ([]byte, error) {
|
||||
c.decrypter.Source = bytes.NewReader(encrypted)
|
||||
return io.ReadAll(c.decrypter)
|
||||
}
|
||||
|
||||
func (c *StreamCryptor) GetKey() string {
|
||||
b64 := base64.RawStdEncoding
|
||||
return fmt.Sprintf("%s.%s.%s.%s",
|
||||
b64.EncodeToString(c.enckey),
|
||||
b64.EncodeToString(c.macKey),
|
||||
b64.EncodeToString(c.encrypter.IV),
|
||||
b64.EncodeToString(c.encrypter.Mac.Sum(nil)),
|
||||
)
|
||||
}
|
||||
|
||||
func writeHash(mac hash.Hash, p []byte) error {
|
||||
m, err := mac.Write(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if m != len(p) {
|
||||
return errors.New("could not write all bytes to hmac")
|
||||
}
|
||||
return nil
|
||||
}
|
100
plugin/crypto/pkg/method/xor.go
Executable file
100
plugin/crypto/pkg/method/xor.go
Executable file
@@ -0,0 +1,100 @@
|
||||
package method
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// SimpleXorCryptor 加密一次
|
||||
type SimpleXorCryptor struct {
|
||||
key []byte
|
||||
}
|
||||
|
||||
func newSimpleXor(cfg Key) (ICryptor, error) {
|
||||
var cryptor *SimpleXorCryptor
|
||||
if cfg.Key == "" {
|
||||
return nil, errors.New("xor cryptor config no key")
|
||||
} else {
|
||||
cryptor = &SimpleXorCryptor{key: []byte(cfg.Key)}
|
||||
}
|
||||
return cryptor, nil
|
||||
}
|
||||
|
||||
// simpleXorEncryptDecrypt 对给定的字节数组进行 XOR 加密和解密
|
||||
// key 是用于加密和解密的密钥
|
||||
func simpleXorEncryptDecrypt(data []byte, key []byte) []byte {
|
||||
dataLen := len(data)
|
||||
result := make([]byte, dataLen)
|
||||
keyLen := len(key)
|
||||
for i := 0; i < dataLen; i += keyLen {
|
||||
end := i + keyLen
|
||||
if end > dataLen {
|
||||
end = dataLen
|
||||
}
|
||||
subtle.XORBytes(result[i:end], data[i:end], key[:end-i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *SimpleXorCryptor) Encrypt(origin []byte) ([]byte, error) {
|
||||
return simpleXorEncryptDecrypt(origin, c.key), nil
|
||||
}
|
||||
|
||||
func (c *SimpleXorCryptor) Decrypt(encrypted []byte) ([]byte, error) {
|
||||
return simpleXorEncryptDecrypt(encrypted, c.key), nil
|
||||
}
|
||||
|
||||
func (c *SimpleXorCryptor) GetKey() string {
|
||||
return base64.RawStdEncoding.EncodeToString(c.key)
|
||||
}
|
||||
|
||||
// 复杂的XOR加密器 加密两次
|
||||
type ComplexXorCryptor struct {
|
||||
key []byte
|
||||
iv []byte
|
||||
}
|
||||
|
||||
func newComplexXor(cfg Key) (ICryptor, error) {
|
||||
var cryptor *ComplexXorCryptor
|
||||
if cfg.Key == "" {
|
||||
return nil, errors.New("xor cryptor config no key")
|
||||
} else {
|
||||
cryptor = &ComplexXorCryptor{key: []byte(cfg.Key), iv: []byte(cfg.Iv)}
|
||||
}
|
||||
return cryptor, nil
|
||||
}
|
||||
|
||||
// complexXorEncryptDecrypt 对给定的字节数组进行 XOR 加密和解密
|
||||
func complexXorEncryptDecrypt(arrayBuffer, key, iv []byte) []byte {
|
||||
// Assuming the key and iv have been provided and are not nil
|
||||
if key == nil || iv == nil {
|
||||
panic("key and iv must not be nil")
|
||||
}
|
||||
|
||||
result := make([]byte, len(arrayBuffer))
|
||||
keyLen := len(key)
|
||||
ivLen := len(iv)
|
||||
|
||||
for i := 0; i < len(result); i++ {
|
||||
result[i] = arrayBuffer[i] ^ (key[i%keyLen] ^ iv[i%ivLen])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *ComplexXorCryptor) Encrypt(origin []byte) ([]byte, error) {
|
||||
return complexXorEncryptDecrypt(origin, c.key, c.iv), nil
|
||||
}
|
||||
|
||||
func (c *ComplexXorCryptor) Decrypt(encrypted []byte) ([]byte, error) {
|
||||
return complexXorEncryptDecrypt(encrypted, c.key, c.iv), nil
|
||||
}
|
||||
|
||||
func (c *ComplexXorCryptor) GetKey() string {
|
||||
return base64.RawStdEncoding.EncodeToString(c.key) + "." + base64.RawStdEncoding.EncodeToString(c.iv)
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterCryptor("xor_s", newSimpleXor)
|
||||
RegisterCryptor("xor_c", newComplexXor)
|
||||
}
|
177
plugin/crypto/pkg/transform.go
Normal file
177
plugin/crypto/pkg/transform.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"github.com/deepch/vdk/codec/h265parser"
|
||||
"m7s.live/v5/pkg"
|
||||
"m7s.live/v5/pkg/codec"
|
||||
"m7s.live/v5/pkg/task"
|
||||
|
||||
"fmt"
|
||||
|
||||
m7s "m7s.live/v5"
|
||||
"m7s.live/v5/plugin/crypto/pkg/method"
|
||||
)
|
||||
|
||||
// GlobalConfig 全局加密配置
|
||||
var GlobalConfig Config
|
||||
|
||||
type Config struct {
|
||||
IsStatic bool `desc:"是否静态密钥" default:"false"`
|
||||
Algo string `desc:"加密算法" default:"aes_ctr"` //加密算法
|
||||
EncryptLen int `desc:"加密字节长度" default:"1024"` //加密字节长度
|
||||
Secret struct {
|
||||
Key string `desc:"加密密钥" default:"your key"` //加密密钥
|
||||
Iv string `desc:"加密向量" default:"your iv"` //加密向量
|
||||
} `desc:"密钥配置"`
|
||||
}
|
||||
|
||||
type Transform struct {
|
||||
m7s.DefaultTransformer
|
||||
cryptor method.ICryptor
|
||||
}
|
||||
|
||||
func NewTransform() m7s.ITransformer {
|
||||
ret := &Transform{}
|
||||
ret.SetDescription(task.OwnerTypeKey, "Crypto")
|
||||
return ret
|
||||
}
|
||||
|
||||
// ValidateAndCreateKey 验证并创建加密密钥
|
||||
func ValidateAndCreateKey(isStatic bool, algo string, secretKey, secretIv, streamPath string) (keyConf method.Key, err error) {
|
||||
if isStatic {
|
||||
switch algo {
|
||||
case "aes_ctr":
|
||||
keyConf.Key = secretKey
|
||||
keyConf.Iv = secretIv
|
||||
if len(keyConf.Iv) != 16 || len(keyConf.Key) != 32 {
|
||||
return keyConf, fmt.Errorf("key or iv length is wrong")
|
||||
}
|
||||
case "xor_s":
|
||||
keyConf.Key = secretKey
|
||||
if len(keyConf.Key) != 32 {
|
||||
return keyConf, fmt.Errorf("key length is wrong")
|
||||
}
|
||||
case "xor_c":
|
||||
keyConf.Key = secretKey
|
||||
keyConf.Iv = secretIv
|
||||
if len(keyConf.Iv) != 16 || len(keyConf.Key) != 32 {
|
||||
return keyConf, fmt.Errorf("key or iv length is wrong")
|
||||
}
|
||||
default:
|
||||
return keyConf, fmt.Errorf("algo type is wrong")
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
动态加密
|
||||
key = md5(密钥+流名称)
|
||||
iv = md5(流名称)前一半
|
||||
*/
|
||||
if secretKey != "" {
|
||||
keyConf.Key = method.Md5Sum(secretKey + streamPath)
|
||||
keyConf.Iv = method.Md5Sum(streamPath)[:16]
|
||||
} else {
|
||||
return keyConf, fmt.Errorf("secret key is empty")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *Transform) Start() error {
|
||||
// 在 Start 时获取并保存配置
|
||||
t.Info("transform job started")
|
||||
|
||||
keyConf, err := ValidateAndCreateKey(GlobalConfig.IsStatic, GlobalConfig.Algo, GlobalConfig.Secret.Key, GlobalConfig.Secret.Iv, t.TransformJob.StreamPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.cryptor, err = method.GetCryptor(GlobalConfig.Algo, keyConf)
|
||||
if err != nil {
|
||||
t.Error("failed to create cryptor", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 使用 TransformJob 的 Subscribe 方法订阅流
|
||||
if err := t.TransformJob.Subscribe(); err != nil {
|
||||
t.Error("failed to subscribe stream", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
t.Info("crypto transform started",
|
||||
"stream", t.TransformJob.StreamPath,
|
||||
"algo", GlobalConfig.Algo,
|
||||
"isStatic", GlobalConfig.IsStatic,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Transform) Go() error {
|
||||
// 创建发布者
|
||||
if err := t.TransformJob.Publish(t.TransformJob.StreamPath + "/crypto"); err != nil {
|
||||
t.Error("failed to create publisher", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 处理音视频流
|
||||
return m7s.PlayBlock(t.TransformJob.Subscriber,
|
||||
func(audio *pkg.RawAudio) (err error) {
|
||||
copyAudio := &pkg.RawAudio{
|
||||
FourCC: audio.FourCC,
|
||||
Timestamp: audio.Timestamp,
|
||||
}
|
||||
audio.Memory.Range(func(b []byte) {
|
||||
copy(copyAudio.NextN(len(b)), b)
|
||||
})
|
||||
return t.TransformJob.Publisher.WriteAudio(copyAudio)
|
||||
},
|
||||
func(video *pkg.H26xFrame) error {
|
||||
// 处理视频帧
|
||||
if video.GetSize() == 0 {
|
||||
return nil
|
||||
}
|
||||
copyVideo := &pkg.H26xFrame{
|
||||
FourCC: video.FourCC,
|
||||
CTS: video.CTS,
|
||||
Timestamp: video.Timestamp,
|
||||
}
|
||||
|
||||
for _, nalu := range video.Nalus {
|
||||
mem := copyVideo.NextN(nalu.Size)
|
||||
copy(mem, nalu.ToBytes())
|
||||
needEncrypt := false
|
||||
if video.FourCC == codec.FourCC_H264 {
|
||||
switch codec.ParseH264NALUType(mem[0]) {
|
||||
case codec.NALU_Non_IDR_Picture, codec.NALU_IDR_Picture:
|
||||
needEncrypt = true
|
||||
}
|
||||
} else if video.FourCC == codec.FourCC_H265 {
|
||||
switch codec.ParseH265NALUType(mem[0]) {
|
||||
case h265parser.NAL_UNIT_CODED_SLICE_BLA_W_LP,
|
||||
h265parser.NAL_UNIT_CODED_SLICE_BLA_W_RADL,
|
||||
h265parser.NAL_UNIT_CODED_SLICE_BLA_N_LP,
|
||||
h265parser.NAL_UNIT_CODED_SLICE_IDR_W_RADL,
|
||||
h265parser.NAL_UNIT_CODED_SLICE_IDR_N_LP,
|
||||
h265parser.NAL_UNIT_CODED_SLICE_CRA:
|
||||
needEncrypt = true
|
||||
}
|
||||
}
|
||||
if needEncrypt {
|
||||
if encBytes, err := t.cryptor.Encrypt(mem); err == nil {
|
||||
copyVideo.Nalus.Append(encBytes)
|
||||
} else {
|
||||
copyVideo.Nalus.Append(mem)
|
||||
}
|
||||
} else {
|
||||
copyVideo.Nalus.Append(mem)
|
||||
}
|
||||
}
|
||||
return t.TransformJob.Publisher.WriteVideo(copyVideo)
|
||||
})
|
||||
}
|
||||
|
||||
func (t *Transform) Dispose() {
|
||||
t.Info("crypto transform disposed",
|
||||
"stream", t.TransformJob.StreamPath,
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user