From c61eb950fbdcef1adbbc902740d96bd6685ed346 Mon Sep 17 00:00:00 2001 From: Rajon Ahmed Date: Fri, 28 Aug 2026 12:51:06 +0800 Subject: [PATCH] upd: essential/file updated helper cipher updated --- .../visible/service/helper/cipher/cipher.go | 641 ++++++++++++++++++ 1 file changed, 641 insertions(+) diff --git a/essential/visible/service/helper/cipher/cipher.go b/essential/visible/service/helper/cipher/cipher.go index e69de29..c6f5386 100644 --- a/essential/visible/service/helper/cipher/cipher.go +++ b/essential/visible/service/helper/cipher/cipher.go @@ -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)) +}