From b866b25c2edd60be4ea6fb8b7f2247364ab0ef49 Mon Sep 17 00:00:00 2001 From: orestonce Date: Mon, 15 May 2023 09:26:38 +0800 Subject: [PATCH] =?UTF-8?q?aes=E8=A7=A3=E5=AF=86=E9=94=99=E8=AF=AFfix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- download.go | 80 +++++++++++++++++++++++++++------------------------- go.mod | 2 +- go.sum | 4 +-- m3u8.go | 71 ++++++++++++++++++++++++++++++++++++++++++++++ m3u8_test.go | 50 ++++++++++++++++++++++++++++++++ 5 files changed, 166 insertions(+), 41 deletions(-) create mode 100644 m3u8.go create mode 100644 m3u8_test.go diff --git a/download.go b/download.go index 06544b9..7779a90 100644 --- a/download.go +++ b/download.go @@ -181,9 +181,9 @@ func (this *downloadEnv) RunDownload(req RunDownload_Req) (resp RunDownload_Resp } } // 获取m3u8地址的内容体 - tsKey, err := this.getM3u8Key(req.M3u8Url, string(m3u8Body)) + encInfo, err := this.getEncryptInfo(req.M3u8Url, string(m3u8Body)) if err != nil { - resp.ErrMsg = "getM3u8Key: " + err.Error() + resp.ErrMsg = "getEncryptInfo: " + err.Error() resp.IsCancel = this.GetIsCancel() return resp } @@ -201,7 +201,7 @@ func (this *downloadEnv) RunDownload(req RunDownload_Req) (resp RunDownload_Resp // 下载ts this.SetProgressBarTitle("[4/6]下载ts") this.speedSetBegin() - err = this.downloader(tsList, downloadDir, tsKey, req.ThreadCount) + err = this.downloader(tsList, downloadDir, encInfo, req.ThreadCount) this.speedClearBytes() if err != nil { resp.ErrMsg = "下载ts文件错误: " + err.Error() @@ -343,34 +343,31 @@ func GetWd() string { return wd } -// 获取m3u8加密的密钥 -func (this *downloadEnv) getM3u8Key(m3u8Url string, html string) (key string, err error) { - key = "" - for _, line := range splitLineWithTrimSpace(html) { - if strings.Contains(line, "#EXT-X-KEY") == false { - continue - } - uriPos := strings.Index(line, "URI") - quotationMarkPos := strings.LastIndex(line, "\"") - if uriPos == -1 || quotationMarkPos == -1 { - continue - } - keyUrl := strings.Split(line[uriPos:quotationMarkPos], "\"")[1] - if !strings.Contains(line, "http") { - var errMsg string - keyUrl, errMsg = resolveRefUrl(m3u8Url, line) - if errMsg != "" { - return "", errors.New(errMsg) - } - } - var res []byte - res, err = this.doGetRequest(keyUrl) - if err != nil { - return "", err - } - return string(res), nil +// 获取m3u8加密的密钥, 可能存在iv +func (this *downloadEnv) getEncryptInfo(m3u8Url string, html string) (info *EncryptInfo, err error) { + keyPart := M3u8Parse(html).GetPart("#EXT-X-KEY") + uri := keyPart.KeyValue["URI"] + if uri == "" { + return nil, nil } - return "", nil + keyUrl, errMsg := resolveRefUrl(m3u8Url, uri) + if errMsg != "" { + return nil, errors.New(errMsg) + } + var res []byte + res, err = this.doGetRequest(keyUrl) + if err != nil { + return nil, err + } + iv, err := hex.DecodeString(strings.TrimPrefix(keyPart.KeyValue["IV"], "0x")) + if err != nil { + return nil, err + } + return &EncryptInfo{ + Method: keyPart.KeyValue["METHOD"], + Key: res, + Iv: iv, + }, nil } func splitLineWithTrimSpace(s string) []string { @@ -405,7 +402,7 @@ func getTsList(m38uUrl string, body string) (tsList []TsInfo, errMsg string) { // 下载ts文件 // @modify: 2020-08-13 修复ts格式SyncByte合并不能播放问题 -func (this *downloadEnv) downloadTsFile(ts TsInfo, download_dir, key string) (err error) { +func (this *downloadEnv) downloadTsFile(ts TsInfo, download_dir string, encInfo *EncryptInfo) (err error) { currPath := fmt.Sprintf("%s/%s", download_dir, ts.Name) if isFileExists(currPath) { return nil @@ -418,9 +415,9 @@ func (this *downloadEnv) downloadTsFile(ts TsInfo, download_dir, key string) (er var origData []byte origData = data // 解密出视频 ts 源文件 - if key != "" { + if encInfo != nil { //解密 ts 文件,算法:aes 128 cbc pack5 - origData, err = AesDecrypt(origData, []byte(key)) + origData, err = AesDecrypt(origData, encInfo) if err != nil { return err } @@ -462,7 +459,7 @@ func (this *downloadEnv) SleepDur(d time.Duration) { } } -func (this *downloadEnv) downloader(tsList []TsInfo, downloadDir string, key string, threadCount int) (err error) { +func (this *downloadEnv) downloader(tsList []TsInfo, downloadDir string, encInfo *EncryptInfo, threadCount int) (err error) { if threadCount <= 0 || threadCount > 1000 { return errors.New("downloadEnv.threadCount invalid: " + strconv.Itoa(threadCount)) } @@ -487,7 +484,7 @@ func (this *downloadEnv) downloader(tsList []TsInfo, downloadDir string, key str this.SleepDur(time.Second * time.Duration(i)) atomic.AddInt32(&this.sleepTh, -1) } - lastErr = this.downloadTsFile(ts, downloadDir, key) + lastErr = this.downloadTsFile(ts, downloadDir, encInfo) if lastErr == nil { break } @@ -549,13 +546,20 @@ func PKCS7UnPadding(origData []byte) []byte { return origData[:(length - unpadding)] } -func AesDecrypt(crypted, key []byte) ([]byte, error) { - block, err := aes.NewCipher(key) +func AesDecrypt(crypted []byte, encInfo *EncryptInfo) ([]byte, error) { + block, err := aes.NewCipher(encInfo.Key) if err != nil { return nil, err } blockSize := block.BlockSize() - blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) + iv := encInfo.Iv + if len(iv) == 0 { + if len(encInfo.Key) > blockSize { + return nil, errors.New(fmt.Sprint("AesDecrypt invalid size ", blockSize, " ", len(encInfo.Key))) + } + iv = encInfo.Key[:blockSize] + } + blockMode := cipher.NewCBCDecrypter(block, iv) origData := make([]byte, len(crypted)) blockMode.CryptBlocks(origData, crypted) origData = PKCS7UnPadding(origData) diff --git a/go.mod b/go.mod index ce22056..545a3d2 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/orestonce/gopool v0.0.0-20220508090328-d7d56d45b171 github.com/spf13/cobra v1.4.0 github.com/yapingcat/gomedia v0.0.0-20221023155149-c5f2f0f45ca5 - golang.org/x/text v0.3.7 + golang.org/x/text v0.3.3 ) require ( diff --git a/go.sum b/go.sum index 278d94a..a7397d6 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/yapingcat/gomedia v0.0.0-20221023155149-c5f2f0f45ca5 h1:dswmlgf8sSrjaQJCGDpmI14sIkHqyIZdP6Jwzl7gVDY= github.com/yapingcat/gomedia v0.0.0-20221023155149-c5f2f0f45ca5/go.mod h1:WSZ59bidJOO40JSJmLqlkBJrjZCtjbKKkygEMfzY/kc= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/m3u8.go b/m3u8.go new file mode 100644 index 0000000..75d1111 --- /dev/null +++ b/m3u8.go @@ -0,0 +1,71 @@ +package m3u8d + +import ( + "strconv" + "strings" +) + +type EncryptInfo struct { + Method string + Key []byte + Iv []byte +} + +type M3u8Content struct { + PartList []M3u8Part `json:",omitempty"` + TsList []string `json:",omitempty"` +} + +type M3u8Part struct { + Tag string `json:",omitempty"` + TextFull string `json:",omitempty"` + KeyValue map[string]string `json:",omitempty"` +} + +func (info M3u8Content) GetPart(tag string) M3u8Part { + for _, one := range info.PartList { + if one.Tag == tag { + return one + } + } + return M3u8Part{} +} + +func M3u8Parse(content string) (info M3u8Content) { + for _, line := range splitLineWithTrimSpace(content) { + if strings.HasPrefix(line, "#") == false { + continue + } + tmp := strings.SplitN(line, ":", 2) + if len(tmp) < 2 { + info.PartList = append(info.PartList, M3u8Part{ + Tag: tmp[0], + }) + continue + } + var p M3u8Part + p.Tag = tmp[0] + p.TextFull = strings.TrimSpace(tmp[1]) + for _, kv := range strings.Split(tmp[1], ",") { + data := strings.SplitN(kv, "=", 2) + if len(data) < 2 { + continue + } + key := data[0] + value := strings.TrimSpace(data[1]) + if strings.HasPrefix(value, "\"") { + var err error + value, err = strconv.Unquote(value) + if err != nil { + continue + } + } + if p.KeyValue == nil { + p.KeyValue = map[string]string{} + } + p.KeyValue[key] = value + } + info.PartList = append(info.PartList, p) + } + return info +} diff --git a/m3u8_test.go b/m3u8_test.go new file mode 100644 index 0000000..e22a646 --- /dev/null +++ b/m3u8_test.go @@ -0,0 +1,50 @@ +package m3u8d + +import ( + "encoding/hex" + "strconv" + "strings" + "testing" +) + +func TestM3u8Parse(t *testing.T) { + info := M3u8Parse(`#EXTM3U +#EXT-X-VERSION:3 +#EXT-X-TARGETDURATION:6 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-KEY:METHOD=AES-128,URI="/20230502/xthms/2000kb/hls/key.key" +`) + part := info.GetPart("#EXT-X-KEY") + if part.KeyValue["METHOD"] != "AES-128" { + panic("method") + } + if part.KeyValue["URI"] != "/20230502/xthms/2000kb/hls/key.key" { + panic("uri") + } +} + +func TestGetFileNameFromUrl(t *testing.T) { + { + part := M3u8Parse(`#EXT-X-KEY:IV=0x10c27a9e3fa363dfe4c44b59b67304b3`).GetPart("#EXT-X-KEY") + iv, err := hex.DecodeString(strings.TrimPrefix(part.KeyValue["IV"], "0x")) + checkErr(err) + if len(iv) != 16 { + panic("iv " + strconv.Quote(string(iv))) + } + } + { + part := M3u8Parse(`#EXT-X-KEY:nothing`).GetPart("#EXT-X-KEY") + iv, err := hex.DecodeString(strings.TrimPrefix(part.KeyValue["IV"], "0x")) + checkErr(err) + if len(iv) != 0 { + panic("iv " + strconv.Quote(string(iv))) + } + } +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +}