Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encrypt tools take base64 in addition to hex key and keyID values #392

Merged
merged 1 commit into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- Nothing yet
### Changed

- mp4.NewUUIDFromHex() changed to more general mp4.NewUUIDFromString()
- cmd/mp4ff-decrypt -key option instead of -k. Takes hex or base64 value
- cmd/mp4ff-encrypt -key and -kid options now take hex or bae64 values

### Added

- mp4.SetUUID() can take base64 string as well as hex-encoded.

## [0.47.0] - 2024-11-12

Expand Down
19 changes: 9 additions & 10 deletions cmd/mp4ff-decrypt/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@
mp4ff-decrypt decrypts a fragmented mp4 file encrypted with Common Encryption scheme cenc or cbcs.
For a media segment, it needs an init segment with encryption information.

Usage of mp4ff-decrypt:
Usage of mp4ff-decrypt:
mp4ff-decrypt [options] infile outfile

mp4ff-decrypt [options] infile outfile
options:

options:

-init string
Path to init file with encryption info (scheme, kid, pssh)
-k string
Required: key (hex)
-version
Get mp4ff version
-init string
Path to init file with encryption info (scheme, kid, pssh)
-key string
Required: key (32 hex or 24 base64 chars)
-version
Get mp4ff version
*/
package main
27 changes: 12 additions & 15 deletions cmd/mp4ff-decrypt/main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main

import (
"encoding/hex"
"errors"
"flag"
"fmt"
Expand All @@ -24,7 +23,7 @@ Usage of %s:

type options struct {
initFilePath string
hexKey string
keyStr string
version bool
}

Expand All @@ -37,7 +36,7 @@ func parseOptions(fs *flag.FlagSet, args []string) (*options, error) {

opts := options{}
fs.StringVar(&opts.initFilePath, "init", "", "Path to init file with encryption info (scheme, kid, pssh)")
fs.StringVar(&opts.hexKey, "k", "", "Required: key (hex)")
fs.StringVar(&opts.keyStr, "key", "", "Required: key (32 hex or 24 base64 chars)")
fs.BoolVar(&opts.version, "version", false, "Get mp4ff version")
err := fs.Parse(args[1:])
return &opts, err
Expand Down Expand Up @@ -74,8 +73,13 @@ func run(args []string) error {
var inFilePath = fs.Arg(0)
var outFilePath = fs.Arg(1)

if opts.hexKey == "" {
return fmt.Errorf("no hex key specified")
if opts.keyStr == "" {
return fmt.Errorf("no key specified")
}

key, err := mp4.UnpackKey(opts.keyStr)
if err != nil {
return fmt.Errorf("unpacking key: %w", err)
}

ifh, err := os.Open(inFilePath)
Expand All @@ -96,22 +100,15 @@ func run(args []string) error {
}
defer inith.Close()
}
err = decryptFile(ifh, inith, ofh, opts.hexKey)

err = decryptFile(ifh, inith, ofh, key)
if err != nil {
return fmt.Errorf("decryptFile: %w", err)
}
return nil
}

func decryptFile(r, initR io.Reader, w io.Writer, hexKey string) error {

if len(hexKey) != 32 {
return fmt.Errorf("hex key must have length 32 chars")
}
key, err := hex.DecodeString(hexKey)
if err != nil {
return err
}
func decryptFile(r, initR io.Reader, w io.Writer, key []byte) error {
inMp4, err := mp4.DecodeFile(r)
if err != nil {
return err
Expand Down
40 changes: 26 additions & 14 deletions cmd/mp4ff-decrypt/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"os"
"path"
"testing"

"github.com/Eyevinn/mp4ff/mp4"
)

func TestNonRunningOptionCases(t *testing.T) {
Expand All @@ -24,12 +26,12 @@ func TestNonRunningOptionCases(t *testing.T) {
{desc: "unknown args", args: []string{"mp4ff-decrypt", "-x"}, err: true},
{desc: "no outfile", args: []string{"mp4ff-decrypt", "infile.mp4"}, err: true},
{desc: "no key", args: []string{"mp4ff-decrypt", "infile.mp4", outFile}, err: true},
{desc: "non-existing infile", args: []string{"mp4ff-decrypt", "-k", key, "infile.mp4", outFile}, err: true},
{desc: "non-existing initfile", args: []string{"mp4ff-decrypt", "-init", "init.mp4", "-k", key, infile, outFile}, err: true},
{desc: "bad infile", args: []string{"mp4ff-decrypt", "-k", key, "main.go", outFile}, err: true},
{desc: "short key", args: []string{"mp4ff-decrypt", "-k", "ab", infile, outFile}, err: true},
{desc: "bad key", args: []string{"mp4ff-decrypt", "-k", badKey, infile, outFile}, err: true},
{desc: "non-encrypted file", args: []string{"mp4ff-decrypt", "-k", key, nonEncryptedFile, outFile}, err: false},
{desc: "non-existing infile", args: []string{"mp4ff-decrypt", "-key", key, "infile.mp4", outFile}, err: true},
{desc: "non-existing initfile", args: []string{"mp4ff-decrypt", "-init", "init.mp4", "-key", key, infile, outFile}, err: true},
{desc: "bad infile", args: []string{"mp4ff-decrypt", "-key", key, "main.go", outFile}, err: true},
{desc: "short key", args: []string{"mp4ff-decrypt", "-key", "ab", infile, outFile}, err: true},
{desc: "bad key", args: []string{"mp4ff-decrypt", "-key", badKey, infile, outFile}, err: true},
{desc: "non-encrypted file", args: []string{"mp4ff-decrypt", "-key", key, nonEncryptedFile, outFile}, err: false},
{desc: "version", args: []string{"mp4ff-decrypt", "-version"}, err: false},
{desc: "help", args: []string{"mp4ff-decrypt", "-h"}, err: false},
}
Expand All @@ -52,38 +54,44 @@ func TestDecodeFiles(t *testing.T) {
initFile string
inFile string
expectedOutFile string
hexKey string
keyHexOrBase64 string
}{
{
desc: "cenc",
inFile: "../../mp4/testdata/prog_8s_enc_dashinit.mp4",
expectedOutFile: "../../mp4/testdata/prog_8s_dec_dashinit.mp4",
hexKey: "63cb5f7184dd4b689a5c5ff11ee6a328",
keyHexOrBase64: "63cb5f7184dd4b689a5c5ff11ee6a328",
},
{
desc: "cenc with base64 key",
inFile: "../../mp4/testdata/prog_8s_enc_dashinit.mp4",
expectedOutFile: "../../mp4/testdata/prog_8s_dec_dashinit.mp4",
keyHexOrBase64: "Y8tfcYTdS2iaXF/xHuajKA==",
},
{
desc: "cbcs",
inFile: "../../mp4/testdata/cbcs.mp4",
expectedOutFile: "../../mp4/testdata/cbcsdec.mp4",
hexKey: "22bdb0063805260307ee5045c0f3835a",
keyHexOrBase64: "22bdb0063805260307ee5045c0f3835a",
},
{
desc: "cbcs audio",
inFile: "../../mp4/testdata/cbcs_audio.mp4",
expectedOutFile: "../../mp4/testdata/cbcs_audiodec.mp4",
hexKey: "5ffd93861fa776e96cccd934898fc1c8",
keyHexOrBase64: "5ffd93861fa776e96cccd934898fc1c8",
},
{
desc: "PIFF audio",
initFile: "testdata/PIFF/audio/init.mp4",
inFile: "testdata/PIFF/audio/segment-1.0001.m4s",
expectedOutFile: "testdata/PIFF/audio/segment-1.0001_dec.m4s",
hexKey: "602a9289bfb9b1995b75ac63f123fc86",
keyHexOrBase64: "602a9289bfb9b1995b75ac63f123fc86",
},
{
desc: "PIFF video",
inFile: "testdata/PIFF/video/complseg-1.0001.mp4",
expectedOutFile: "testdata/PIFF/video/complseg-1.0001_dec.mp4",
hexKey: "602a9289bfb9b1995b75ac63f123fc86",
keyHexOrBase64: "602a9289bfb9b1995b75ac63f123fc86",
},
}
tmpDir := t.TempDir()
Expand All @@ -94,7 +102,7 @@ func TestDecodeFiles(t *testing.T) {
if c.initFile != "" {
args = append(args, "-init", c.initFile)
}
args = append(args, "-k", c.hexKey, c.inFile, outFile)
args = append(args, "-key", c.keyHexOrBase64, c.inFile, outFile)
err := run(args)
if err != nil {
t.Error(err)
Expand Down Expand Up @@ -127,7 +135,11 @@ func BenchmarkDecodeCenc(b *testing.B) {
for i := 0; i < b.N; i++ {
inBuf := bytes.NewBuffer(raw)
outBuf.Reset()
err = decryptFile(inBuf, nil, outBuf, hexKey)
key, err := mp4.UnpackKey(hexKey)
if err != nil {
b.Error(err)
}
err = decryptFile(inBuf, nil, outBuf, key)
if err != nil {
b.Error(err)
}
Expand Down
36 changes: 18 additions & 18 deletions cmd/mp4ff-encrypt/doc.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
/*
mp4ff-encrypt encrypts a fragmented mp4 file using Common Encryption with cenc or cbcs scheme.
A combined fragmented file with init segment and media segment(s) will be encrypted.
For a pure media segment, an init segment with encryption information is needed
For a pure media segment, an init segment with encryption information is needed.

Usage of mp4ff-encrypt:
Usage of mp4ff-encrypt:

mp4ff-encrypt [options] infile outfile
mp4ff-encrypt [options] infile outfile

options:
options:

-init string
Path to init file with encryption info (scheme, kid, pssh)
-iv string
Required: iv (16 or 32 hex chars)
-key string
Required: key (32 hex chars)
-kid string
key id (32 hex chars). Required if initFilePath empty
-pssh string
file with one or more pssh box(es) in binary format. Will be added at end of moov box
-scheme string
cenc or cbcs. Required if initFilePath empty (default "cenc")
-version
Get mp4ff version
-init string
Path to init file with encryption info (scheme, kid, pssh)
-iv string
Required: iv (16 or 32 hex chars)
-key string
Required: key (32 hex or 24 base64 chars)
-kid string
key id (32 hex or 24 base64 chars). Required if initFilePath empty
-pssh string
file with one or more pssh box(es) in binary format. Will be added at end of moov box
-scheme string
cenc or cbcs. Required if initFilePath empty (default "cenc")
-version
Get mp4ff version
*/
package main
31 changes: 15 additions & 16 deletions cmd/mp4ff-encrypt/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ Usage of %s:

type options struct {
initFile string
kidHex string
keyHex string
kidStr string
keyStr string
ivHex string
scheme string
psshFile string
Expand All @@ -43,8 +43,8 @@ func parseOptions(fs *flag.FlagSet, args []string) (*options, error) {
opts := options{}

fs.StringVar(&opts.initFile, "init", "", "Path to init file with encryption info (scheme, kid, pssh)")
fs.StringVar(&opts.kidHex, "kid", "", "key id (32 hex chars). Required if initFilePath empty")
fs.StringVar(&opts.keyHex, "key", "", "Required: key (32 hex chars)")
fs.StringVar(&opts.kidStr, "kid", "", "key id (32 hex or 24 base64 chars). Required if initFilePath empty")
fs.StringVar(&opts.keyStr, "key", "", "Required: key (32 hex or 24 base64 chars)")
fs.StringVar(&opts.ivHex, "iv", "", "Required: iv (16 or 32 hex chars)")
fs.StringVar(&opts.scheme, "scheme", "cenc", "cenc or cbcs. Required if initFilePath empty")
fs.StringVar(&opts.psshFile, "pssh", "", "file with one or more pssh box(es) in binary format. Will be added at end of moov box")
Expand Down Expand Up @@ -85,7 +85,7 @@ func run(args []string) error {
var inFilePath = fs.Arg(0)
var outFilePath = fs.Arg(1)

if opts.keyHex == "" || opts.ivHex == "" {
if opts.keyStr == "" || opts.ivHex == "" {
fs.Usage()
return fmt.Errorf("need both key and iv")
}
Expand Down Expand Up @@ -127,15 +127,15 @@ func run(args []string) error {
}
}

err = encryptFile(ifh, ofh, initSeg, opts.scheme, opts.kidHex, opts.keyHex, opts.ivHex, psshData)
err = encryptFile(ifh, ofh, initSeg, opts.scheme, opts.kidStr, opts.keyStr, opts.ivHex, psshData)
if err != nil {
return fmt.Errorf("encryptFile: %w", err)
}
return nil
}

func encryptFile(ifh io.Reader, ofh io.Writer, initSeg *mp4.InitSegment,
scheme, kidHex, keyHex, ivHex string, psshData []byte) error {
scheme, kidStr, keyStr, ivHex string, psshData []byte) error {

if len(ivHex) != 32 && len(ivHex) != 16 {
return fmt.Errorf("hex iv must have length 16 or 32 chars; %d", len(ivHex))
Expand All @@ -145,23 +145,22 @@ func encryptFile(ifh io.Reader, ofh io.Writer, initSeg *mp4.InitSegment,
return fmt.Errorf("invalid iv %s", ivHex)
}

if len(keyHex) != 32 {
return fmt.Errorf("hex key must have length 32 chars: %d", len(keyHex))
if len(keyStr) != 32 {
return fmt.Errorf("hex key must have length 32 chars: %d", len(keyStr))
}
key, err := hex.DecodeString(keyHex)
key, err := mp4.UnpackKey(keyStr)
if err != nil {
return fmt.Errorf("invalid key %s", keyHex)
return fmt.Errorf("invalid key %s, %w", keyStr, err)
}

var kidUUID mp4.UUID
if initSeg == nil {
if len(kidHex) != 32 {
return fmt.Errorf("hex key id must have length 32 chars: %d", len(kidHex))
}
kidUUID, err = mp4.NewUUIDFromHex(kidHex)
kid, err := mp4.UnpackKey(kidStr)
if err != nil {
return fmt.Errorf("invalid kid %s", kidHex)
return fmt.Errorf("invalid key ID %s: %w", kidStr, err)
}
kidHex := hex.EncodeToString(kid)
kidUUID, _ = mp4.NewUUIDFromString(kidHex)
if scheme != "cenc" && scheme != "cbcs" {
return fmt.Errorf("scheme must be cenc or cbcs: %s", scheme)
}
Expand Down
2 changes: 1 addition & 1 deletion mp4/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func TestEncryptDecrypt(t *testing.T) {
ivHex16 := "ffeeddccbbaa99887766554433221100"
kidHex := "11112222333344445555666677778888"
key, _ := hex.DecodeString(keyHex)
kidUUID, _ := NewUUIDFromHex(kidHex)
kidUUID, _ := NewUUIDFromString(kidHex)
psshFile := "testdata/pssh.bin"
psh, err := os.Open(psshFile)
if err != nil {
Expand Down
26 changes: 0 additions & 26 deletions mp4/pssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,6 @@ const (
UUID_W3C_COMMON = "1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"
)

// UUID - 16-byte KeyID or SystemID
type UUID []byte

func (u UUID) String() string {
h := hex.EncodeToString(u)
if len(u) != 16 {
return h
}
return fmt.Sprintf("%s-%s-%s-%s-%s", h[0:8], h[8:12], h[12:16], h[16:20], h[20:32])
}

// NewUUIDFromHex creates a UUID from a hexadecimal string with 32 chars or 36 chars (with dashes)
func NewUUIDFromHex(h string) (UUID, error) {
if len(h) == 36 {
h = strings.ReplaceAll(h, "-", "")
}
if len(h) != 32 {
return nil, fmt.Errorf("hex has %d chars, not 32", len(h))
}
s, err := hex.DecodeString(h)
if err != nil {
return nil, err
}
return UUID(s), nil
}

// ProtectionSystemName returns name of protection system if known.
func ProtectionSystemName(systemID UUID) string {
uStr := systemID.String()
Expand Down
Loading
Loading