upd: essential/file updated
helper cipher updated
This commit is contained in:
parent
964ddac31b
commit
c729c54c60
@ -0,0 +1,641 @@
|
|||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Description
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Name:
|
||||||
|
| - Cipher
|
||||||
|
|
|
||||||
|
| Purpose:
|
||||||
|
| - Provide reusable AES-256 encryption and decryption for file content.
|
||||||
|
| - Support standard and salted encryption formats through a unified interface.
|
||||||
|
|
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Instruction
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Guideline:
|
||||||
|
| - Use encrypt-standard to encrypt file content using AES-256 without salt.
|
||||||
|
| - Use encrypt-salted to encrypt file content using AES-256 with a cryptographically secure generated salt.
|
||||||
|
| - Use decrypt-standard to decrypt AES-256 encrypted file content without salt metadata.
|
||||||
|
| - Use decrypt-salted to decrypt self-contained encrypted files with embedded signature, version, salt, and ciphertext metadata.
|
||||||
|
| - Supply the AES-256 compatible key, input file path, and output file path through the configuration.
|
||||||
|
|
|
||||||
|
| Example:
|
||||||
|
| - ObrimCipher("encrypt-standard", map[string]any{
|
||||||
|
| "key": key,
|
||||||
|
| "input": inputPath,
|
||||||
|
| "output": outputPath,
|
||||||
|
| })
|
||||||
|
|
|
||||||
|
| - ObrimCipher("encrypt-salted", map[string]any{
|
||||||
|
| "key": key,
|
||||||
|
| "input": inputPath,
|
||||||
|
| "output": outputPath,
|
||||||
|
| })
|
||||||
|
|
|
||||||
|
| - ObrimCipher("decrypt-standard", map[string]any{
|
||||||
|
| "key": key,
|
||||||
|
| "input": inputPath,
|
||||||
|
| "output": outputPath,
|
||||||
|
| })
|
||||||
|
|
|
||||||
|
| - ObrimCipher("decrypt-salted", map[string]any{
|
||||||
|
| "key": key,
|
||||||
|
| "input": inputPath,
|
||||||
|
| "output": outputPath,
|
||||||
|
| })
|
||||||
|
|
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Credit
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Contributor:
|
||||||
|
| - Rajon Ahmed
|
||||||
|
| - Blockonite
|
||||||
|
|
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cipher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Utility execution type identifiers.
|
||||||
|
const (
|
||||||
|
obrimCipherTypeEncryptStandard = "encrypt-standard"
|
||||||
|
obrimCipherTypeEncryptSalted = "encrypt-salted"
|
||||||
|
obrimCipherTypeDecryptStandard = "decrypt-standard"
|
||||||
|
obrimCipherTypeDecryptSalted = "decrypt-salted"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cipher format metadata values.
|
||||||
|
const (
|
||||||
|
obrimCipherAlgorithm = "AES-256-GCM"
|
||||||
|
obrimCipherFormat = "OBRIM-CIPHER"
|
||||||
|
obrimCipherVersion = "1"
|
||||||
|
obrimCipherSaltSize = 32
|
||||||
|
)
|
||||||
|
|
||||||
|
// Utility execution success result codes.
|
||||||
|
const (
|
||||||
|
obrimCipherSuccessEncryptStandard = "SUCCESS_ENCRYPT_STANDARD"
|
||||||
|
obrimCipherSuccessEncryptSalted = "SUCCESS_ENCRYPT_SALTED"
|
||||||
|
obrimCipherSuccessDecryptStandard = "SUCCESS_DECRYPT_STANDARD"
|
||||||
|
obrimCipherSuccessDecryptSalted = "SUCCESS_DECRYPT_SALTED"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Utility execution failure result codes.
|
||||||
|
const (
|
||||||
|
obrimCipherFailureInvalidType = "FAILURE_INVALID_TYPE"
|
||||||
|
obrimCipherFailureInvalidConfig = "FAILURE_INVALID_CONFIG"
|
||||||
|
obrimCipherFailureMissingKey = "FAILURE_MISSING_KEY"
|
||||||
|
obrimCipherFailureInvalidKey = "FAILURE_INVALID_KEY"
|
||||||
|
obrimCipherFailureMissingInput = "FAILURE_MISSING_INPUT"
|
||||||
|
obrimCipherFailureInvalidInput = "FAILURE_INVALID_INPUT"
|
||||||
|
obrimCipherFailureMissingOutput = "FAILURE_MISSING_OUTPUT"
|
||||||
|
obrimCipherFailureInvalidOutput = "FAILURE_INVALID_OUTPUT"
|
||||||
|
obrimCipherFailureExternalSalt = "FAILURE_EXTERNAL_SALT"
|
||||||
|
obrimCipherFailureReadInput = "FAILURE_READ_INPUT"
|
||||||
|
obrimCipherFailureWriteOutput = "FAILURE_WRITE_OUTPUT"
|
||||||
|
obrimCipherFailureCipherInitialization = "FAILURE_CIPHER_INITIALIZATION"
|
||||||
|
obrimCipherFailureNonceGeneration = "FAILURE_NONCE_GENERATION"
|
||||||
|
obrimCipherFailureEncryption = "FAILURE_ENCRYPTION"
|
||||||
|
obrimCipherFailureDecryption = "FAILURE_DECRYPTION"
|
||||||
|
obrimCipherFailureSaltGeneration = "FAILURE_SALT_GENERATION"
|
||||||
|
obrimCipherFailureHeaderBuild = "FAILURE_HEADER_BUILD"
|
||||||
|
obrimCipherFailureSignature = "FAILURE_INVALID_SIGNATURE"
|
||||||
|
obrimCipherFailureVersion = "FAILURE_INVALID_VERSION"
|
||||||
|
obrimCipherFailureSaltExtraction = "FAILURE_SALT_EXTRACTION"
|
||||||
|
obrimCipherFailurePayloadExtraction = "FAILURE_PAYLOAD_EXTRACTION"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Standardized utility output.
|
||||||
|
type obrimCipherOutput struct {
|
||||||
|
Status bool
|
||||||
|
Code string
|
||||||
|
Payload any
|
||||||
|
}
|
||||||
|
|
||||||
|
// Salted encrypted file header.
|
||||||
|
type obrimCipherHeader struct {
|
||||||
|
Signature []byte
|
||||||
|
Version []byte
|
||||||
|
Salt []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standardized operation payload.
|
||||||
|
type obrimCipherPayload struct {
|
||||||
|
Operation string
|
||||||
|
Algorithm string
|
||||||
|
Format string
|
||||||
|
Version string
|
||||||
|
InputPath string
|
||||||
|
OutputPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObrimCipher executes encryption or decryption operations through a unified interface.
|
||||||
|
func ObrimCipher(typeName string, config map[string]any) map[string]any {
|
||||||
|
if code := obrimCipherValidateInput(typeName, config); code != "" {
|
||||||
|
return obrimCipherBuildOutput(false, code, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, code := obrimCipherRouteRequest(typeName, config)
|
||||||
|
if code != "" {
|
||||||
|
return obrimCipherBuildOutput(false, code, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return obrimCipherBuildOutput(true, code, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// obrimCipherValidateInput validates the requested operation and its configuration.
|
||||||
|
func obrimCipherValidateInput(typeName string, config map[string]any) string {
|
||||||
|
switch typeName {
|
||||||
|
case obrimCipherTypeEncryptStandard,
|
||||||
|
obrimCipherTypeEncryptSalted,
|
||||||
|
obrimCipherTypeDecryptStandard,
|
||||||
|
obrimCipherTypeDecryptSalted:
|
||||||
|
default:
|
||||||
|
return obrimCipherFailureInvalidType
|
||||||
|
}
|
||||||
|
|
||||||
|
if config == nil {
|
||||||
|
return obrimCipherFailureInvalidConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
key, ok := config["key"]
|
||||||
|
if !ok || key == nil {
|
||||||
|
return obrimCipherFailureMissingKey
|
||||||
|
}
|
||||||
|
|
||||||
|
if !obrimCipherValidKey(key) {
|
||||||
|
return obrimCipherFailureInvalidKey
|
||||||
|
}
|
||||||
|
|
||||||
|
input, ok := config["input"]
|
||||||
|
if !ok || input == nil {
|
||||||
|
return obrimCipherFailureMissingInput
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPath, ok := input.(string)
|
||||||
|
if !ok || inputPath == "" {
|
||||||
|
return obrimCipherFailureInvalidInput
|
||||||
|
}
|
||||||
|
|
||||||
|
output, ok := config["output"]
|
||||||
|
if !ok || output == nil {
|
||||||
|
return obrimCipherFailureMissingOutput
|
||||||
|
}
|
||||||
|
|
||||||
|
outputPath, ok := output.(string)
|
||||||
|
if !ok || outputPath == "" {
|
||||||
|
return obrimCipherFailureInvalidOutput
|
||||||
|
}
|
||||||
|
|
||||||
|
switch typeName {
|
||||||
|
case obrimCipherTypeEncryptSalted, obrimCipherTypeDecryptSalted:
|
||||||
|
if _, exists := config["salt"]; exists {
|
||||||
|
return obrimCipherFailureExternalSalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// obrimCipherRouteRequest routes a validated request to its operation-specific implementation.
|
||||||
|
func obrimCipherRouteRequest(typeName string, config map[string]any) (map[string]any, string) {
|
||||||
|
switch typeName {
|
||||||
|
case obrimCipherTypeEncryptStandard:
|
||||||
|
return obrimCipherEncryptStandard(config)
|
||||||
|
case obrimCipherTypeEncryptSalted:
|
||||||
|
return obrimCipherEncryptSalted(config)
|
||||||
|
case obrimCipherTypeDecryptStandard:
|
||||||
|
return obrimCipherDecryptStandard(config)
|
||||||
|
case obrimCipherTypeDecryptSalted:
|
||||||
|
return obrimCipherDecryptSalted(config)
|
||||||
|
default:
|
||||||
|
return nil, obrimCipherFailureInvalidType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// obrimCipherBuildOutput builds the standardized utility output.
|
||||||
|
func obrimCipherBuildOutput(status bool, code string, payload map[string]any) map[string]any {
|
||||||
|
return map[string]any{
|
||||||
|
"status": status,
|
||||||
|
"code": code,
|
||||||
|
"payload": payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// obrimCipherEncryptStandard encrypts file content without salt metadata.
|
||||||
|
func obrimCipherEncryptStandard(config map[string]any) (map[string]any, string) {
|
||||||
|
key, ok := obrimCipherKey(config["key"])
|
||||||
|
if !ok {
|
||||||
|
return nil, obrimCipherFailureInvalidKey
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPath, outputPath, ok := obrimCipherPaths(config)
|
||||||
|
if !ok {
|
||||||
|
return nil, obrimCipherFailureInvalidConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
plaintext, err := os.ReadFile(inputPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, obrimCipherFailureReadInput
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext, code := obrimCipherEncrypt(key, plaintext, nil)
|
||||||
|
if code != "" {
|
||||||
|
return nil, code
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(outputPath, ciphertext, 0600); err != nil {
|
||||||
|
return nil, obrimCipherFailureWriteOutput
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]any{
|
||||||
|
"operation": obrimCipherTypeEncryptStandard,
|
||||||
|
"algorithm": obrimCipherAlgorithm,
|
||||||
|
"inputPath": inputPath,
|
||||||
|
"outputPath": outputPath,
|
||||||
|
}, obrimCipherSuccessEncryptStandard
|
||||||
|
}
|
||||||
|
|
||||||
|
// obrimCipherGenerateSalt generates a cryptographically secure salt.
|
||||||
|
func obrimCipherGenerateSalt() ([]byte, string) {
|
||||||
|
salt := make([]byte, obrimCipherSaltSize)
|
||||||
|
|
||||||
|
if _, err := rand.Read(salt); err != nil {
|
||||||
|
return nil, obrimCipherFailureSaltGeneration
|
||||||
|
}
|
||||||
|
|
||||||
|
return salt, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// obrimCipherBuildHeader builds the salted cipher signature, version, and salt header.
|
||||||
|
func obrimCipherBuildHeader(salt []byte) ([]byte, string) {
|
||||||
|
if len(salt) != obrimCipherSaltSize {
|
||||||
|
return nil, obrimCipherFailureHeaderBuild
|
||||||
|
}
|
||||||
|
|
||||||
|
signature := []byte(obrimCipherFormat)
|
||||||
|
version := []byte(obrimCipherVersion)
|
||||||
|
|
||||||
|
header := make([]byte, 0, len(signature)+len(version)+len(salt))
|
||||||
|
header = append(header, signature...)
|
||||||
|
header = append(header, version...)
|
||||||
|
header = append(header, salt...)
|
||||||
|
|
||||||
|
return header, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// obrimCipherEncryptSalted encrypts file content with embedded salt metadata.
|
||||||
|
func obrimCipherEncryptSalted(config map[string]any) (map[string]any, string) {
|
||||||
|
key, ok := obrimCipherKey(config["key"])
|
||||||
|
if !ok {
|
||||||
|
return nil, obrimCipherFailureInvalidKey
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPath, outputPath, ok := obrimCipherPaths(config)
|
||||||
|
if !ok {
|
||||||
|
return nil, obrimCipherFailureInvalidConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
plaintext, err := os.ReadFile(inputPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, obrimCipherFailureReadInput
|
||||||
|
}
|
||||||
|
|
||||||
|
salt, code := obrimCipherGenerateSalt()
|
||||||
|
if code != "" {
|
||||||
|
return nil, code
|
||||||
|
}
|
||||||
|
|
||||||
|
header, code := obrimCipherBuildHeader(salt)
|
||||||
|
if code != "" {
|
||||||
|
return nil, code
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext, code := obrimCipherEncrypt(key, plaintext, salt)
|
||||||
|
if code != "" {
|
||||||
|
return nil, code
|
||||||
|
}
|
||||||
|
|
||||||
|
output := make([]byte, 0, len(header)+len(ciphertext))
|
||||||
|
output = append(output, header...)
|
||||||
|
output = append(output, ciphertext...)
|
||||||
|
|
||||||
|
if err := os.WriteFile(outputPath, output, 0600); err != nil {
|
||||||
|
return nil, obrimCipherFailureWriteOutput
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]any{
|
||||||
|
"operation": obrimCipherTypeEncryptSalted,
|
||||||
|
"algorithm": obrimCipherAlgorithm,
|
||||||
|
"format": obrimCipherFormat,
|
||||||
|
"version": obrimCipherVersion,
|
||||||
|
"inputPath": inputPath,
|
||||||
|
"outputPath": outputPath,
|
||||||
|
}, obrimCipherSuccessEncryptSalted
|
||||||
|
}
|
||||||
|
|
||||||
|
// obrimCipherDecryptStandard decrypts file content without salt metadata.
|
||||||
|
func obrimCipherDecryptStandard(config map[string]any) (map[string]any, string) {
|
||||||
|
key, ok := obrimCipherKey(config["key"])
|
||||||
|
if !ok {
|
||||||
|
return nil, obrimCipherFailureInvalidKey
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPath, outputPath, ok := obrimCipherPaths(config)
|
||||||
|
if !ok {
|
||||||
|
return nil, obrimCipherFailureInvalidConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext, err := os.ReadFile(inputPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, obrimCipherFailureReadInput
|
||||||
|
}
|
||||||
|
|
||||||
|
plaintext, code := obrimCipherDecrypt(key, ciphertext, nil)
|
||||||
|
if code != "" {
|
||||||
|
return nil, code
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(outputPath, plaintext, 0600); err != nil {
|
||||||
|
return nil, obrimCipherFailureWriteOutput
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]any{
|
||||||
|
"operation": obrimCipherTypeDecryptStandard,
|
||||||
|
"algorithm": obrimCipherAlgorithm,
|
||||||
|
"inputPath": inputPath,
|
||||||
|
"outputPath": outputPath,
|
||||||
|
}, obrimCipherSuccessDecryptStandard
|
||||||
|
}
|
||||||
|
|
||||||
|
// obrimCipherParseHeader parses the signature and version metadata.
|
||||||
|
func obrimCipherParseHeader(data []byte) (obrimCipherHeader, string) {
|
||||||
|
signatureLength := len(obrimCipherFormat)
|
||||||
|
versionLength := len(obrimCipherVersion)
|
||||||
|
|
||||||
|
minimumLength := signatureLength + versionLength + obrimCipherSaltSize
|
||||||
|
if len(data) < minimumLength {
|
||||||
|
return obrimCipherHeader{}, obrimCipherFailureSignature
|
||||||
|
}
|
||||||
|
|
||||||
|
signature := data[:signatureLength]
|
||||||
|
if string(signature) != obrimCipherFormat {
|
||||||
|
return obrimCipherHeader{}, obrimCipherFailureSignature
|
||||||
|
}
|
||||||
|
|
||||||
|
versionStart := signatureLength
|
||||||
|
versionEnd := versionStart + versionLength
|
||||||
|
version := data[versionStart:versionEnd]
|
||||||
|
|
||||||
|
if string(version) != obrimCipherVersion {
|
||||||
|
return obrimCipherHeader{}, obrimCipherFailureVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
saltStart := versionEnd
|
||||||
|
saltEnd := saltStart + obrimCipherSaltSize
|
||||||
|
|
||||||
|
return obrimCipherHeader{
|
||||||
|
Signature: append([]byte(nil), signature...),
|
||||||
|
Version: append([]byte(nil), version...),
|
||||||
|
Salt: append([]byte(nil), data[saltStart:saltEnd]...),
|
||||||
|
}, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// obrimCipherExtractSalt extracts the embedded salt from a parsed salted file.
|
||||||
|
func obrimCipherExtractSalt(data []byte, header obrimCipherHeader) ([]byte, string) {
|
||||||
|
if len(header.Salt) != obrimCipherSaltSize {
|
||||||
|
return nil, obrimCipherFailureSaltExtraction
|
||||||
|
}
|
||||||
|
|
||||||
|
signatureLength := len(header.Signature)
|
||||||
|
versionLength := len(header.Version)
|
||||||
|
saltStart := signatureLength + versionLength
|
||||||
|
saltEnd := saltStart + obrimCipherSaltSize
|
||||||
|
|
||||||
|
if saltStart < 0 || saltEnd > len(data) {
|
||||||
|
return nil, obrimCipherFailureSaltExtraction
|
||||||
|
}
|
||||||
|
|
||||||
|
return append([]byte(nil), data[saltStart:saltEnd]...), ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// obrimCipherExtractPayload extracts the encrypted payload after the salted header.
|
||||||
|
func obrimCipherExtractPayload(data []byte, header obrimCipherHeader) ([]byte, string) {
|
||||||
|
payloadStart := len(header.Signature) + len(header.Version) + len(header.Salt)
|
||||||
|
|
||||||
|
if payloadStart >= len(data) {
|
||||||
|
return nil, obrimCipherFailurePayloadExtraction
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := data[payloadStart:]
|
||||||
|
if len(payload) <= 0 {
|
||||||
|
return nil, obrimCipherFailurePayloadExtraction
|
||||||
|
}
|
||||||
|
|
||||||
|
return append([]byte(nil), payload...), ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// obrimCipherDecryptSalted decrypts file content using its embedded salt metadata.
|
||||||
|
func obrimCipherDecryptSalted(config map[string]any) (map[string]any, string) {
|
||||||
|
key, ok := obrimCipherKey(config["key"])
|
||||||
|
if !ok {
|
||||||
|
return nil, obrimCipherFailureInvalidKey
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPath, outputPath, ok := obrimCipherPaths(config)
|
||||||
|
if !ok {
|
||||||
|
return nil, obrimCipherFailureInvalidConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(inputPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, obrimCipherFailureReadInput
|
||||||
|
}
|
||||||
|
|
||||||
|
header, code := obrimCipherParseHeader(data)
|
||||||
|
if code != "" {
|
||||||
|
return nil, code
|
||||||
|
}
|
||||||
|
|
||||||
|
salt, code := obrimCipherExtractSalt(data, header)
|
||||||
|
if code != "" {
|
||||||
|
return nil, code
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, code := obrimCipherExtractPayload(data, header)
|
||||||
|
if code != "" {
|
||||||
|
return nil, code
|
||||||
|
}
|
||||||
|
|
||||||
|
plaintext, code := obrimCipherDecrypt(key, payload, salt)
|
||||||
|
if code != "" {
|
||||||
|
return nil, code
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(outputPath, plaintext, 0600); err != nil {
|
||||||
|
return nil, obrimCipherFailureWriteOutput
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]any{
|
||||||
|
"operation": obrimCipherTypeDecryptSalted,
|
||||||
|
"algorithm": obrimCipherAlgorithm,
|
||||||
|
"format": string(header.Signature),
|
||||||
|
"version": string(header.Version),
|
||||||
|
"inputPath": inputPath,
|
||||||
|
"outputPath": outputPath,
|
||||||
|
}, obrimCipherSuccessDecryptSalted
|
||||||
|
}
|
||||||
|
|
||||||
|
// obrimCipherEncrypt performs AES-256-GCM encryption and prefixes the nonce to the ciphertext.
|
||||||
|
func obrimCipherEncrypt(key []byte, plaintext []byte, salt []byte) ([]byte, string) {
|
||||||
|
derivedKey := obrimCipherDeriveKey(key, salt)
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(derivedKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, obrimCipherFailureCipherInitialization
|
||||||
|
}
|
||||||
|
|
||||||
|
aead, err := cipher.NewGCM(block)
|
||||||
|
if err != nil {
|
||||||
|
return nil, obrimCipherFailureCipherInitialization
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce := make([]byte, aead.NonceSize())
|
||||||
|
if _, err := rand.Read(nonce); err != nil {
|
||||||
|
return nil, obrimCipherFailureNonceGeneration
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext := aead.Seal(nil, nonce, plaintext, nil)
|
||||||
|
|
||||||
|
output := make([]byte, 0, len(nonce)+len(ciphertext))
|
||||||
|
output = append(output, nonce...)
|
||||||
|
output = append(output, ciphertext...)
|
||||||
|
|
||||||
|
return output, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// obrimCipherDecrypt performs AES-256-GCM decryption after extracting the nonce.
|
||||||
|
func obrimCipherDecrypt(key []byte, ciphertext []byte, salt []byte) ([]byte, string) {
|
||||||
|
derivedKey := obrimCipherDeriveKey(key, salt)
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(derivedKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, obrimCipherFailureCipherInitialization
|
||||||
|
}
|
||||||
|
|
||||||
|
aead, err := cipher.NewGCM(block)
|
||||||
|
if err != nil {
|
||||||
|
return nil, obrimCipherFailureCipherInitialization
|
||||||
|
}
|
||||||
|
|
||||||
|
nonceSize := aead.NonceSize()
|
||||||
|
if len(ciphertext) <= nonceSize {
|
||||||
|
return nil, obrimCipherFailureDecryption
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce := ciphertext[:nonceSize]
|
||||||
|
payload := ciphertext[nonceSize:]
|
||||||
|
|
||||||
|
plaintext, err := aead.Open(nil, nonce, payload, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, obrimCipherFailureDecryption
|
||||||
|
}
|
||||||
|
|
||||||
|
return plaintext, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// obrimCipherDeriveKey derives the AES-256 key from the supplied key and optional salt.
|
||||||
|
func obrimCipherDeriveKey(key []byte, salt []byte) []byte {
|
||||||
|
if len(salt) == 0 {
|
||||||
|
return append([]byte(nil), key...)
|
||||||
|
}
|
||||||
|
|
||||||
|
digest := sha256.New()
|
||||||
|
_, _ = digest.Write(key)
|
||||||
|
_, _ = digest.Write(salt)
|
||||||
|
|
||||||
|
return digest.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// obrimCipherKey normalizes a supported AES-256 key value.
|
||||||
|
func obrimCipherKey(value any) ([]byte, bool) {
|
||||||
|
switch key := value.(type) {
|
||||||
|
case string:
|
||||||
|
if len([]byte(key)) != 32 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(key), true
|
||||||
|
case []byte:
|
||||||
|
if len(key) != 32 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return append([]byte(nil), key...), true
|
||||||
|
default:
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// obrimCipherValidKey validates that a supplied key is AES-256 compatible.
|
||||||
|
func obrimCipherValidKey(value any) bool {
|
||||||
|
_, ok := obrimCipherKey(value)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// obrimCipherPaths resolves and validates the configured input and output paths.
|
||||||
|
func obrimCipherPaths(config map[string]any) (string, string, bool) {
|
||||||
|
input, inputOK := config["input"].(string)
|
||||||
|
output, outputOK := config["output"].(string)
|
||||||
|
|
||||||
|
if !inputOK || !outputOK || input == "" || output == "" {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPath, err := filepath.Abs(input)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
outputPath, err := filepath.Abs(output)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
if inputPath == outputPath {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(inputPath); err != nil {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return inputPath, outputPath, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// obrimCipherError provides a deterministic error representation for internal callers.
|
||||||
|
func obrimCipherError(code string) error {
|
||||||
|
return errors.New(fmt.Sprintf("cipher: %s", code))
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user