upd: essential/file updated

helper cipher updated
This commit is contained in:
Rajon Ahmed 2026-08-28 12:51:05 +08:00
parent 3500a9248d
commit c18176f240
Signed by: engrrajonahmed
GPG Key ID: 19C3D328D8C8F65F

View File

@ -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))
}