security: Đóng gói và bảo vệ khóa giải mã nội bộ trong binary, gỡ bỏ hoàn toàn khỏi cấu hình và tài liệu

This commit is contained in:
ongtrieuhau
2026-10-27 03:46:27 -07:00
parent f3230fcd9d
commit bc16c720cf
6 changed files with 22 additions and 60 deletions
-1
View File
@@ -42,7 +42,6 @@
"current_version": "v1.0.2" "current_version": "v1.0.2"
}, },
"license": { "license": {
"crypto_key": "ENC(R8h55AZzKMVH6QO+X/mBHvoMBLSBOZ9HCXSH2CbLrEwCUgGzFf7aJ5p8VCv7DZAMY6TkJQemYZIcXRS0)",
"key": "U2FsdGVkX18Zsq52FPbB7FS1QM7CwWh+t1Esi96gUMFILJD0vZXM69uQBC4sP7TjLVOiJPZt0KIq17iql+hXehLCVSbCEhwKKm+zNab+u2Swah63A52hz0KMJXddLRZ6vanmP2goLTzY7749HsFVRDq+3bdbi8fdUZCpAsSTEiqC2cRTpF5WVh0SHt1wX/i42YSQgOYPeXQtg+lRx/URk4p7lYSVQOYWXglp99kO+Va1ADoNcTgrfz3aaAxAkj0S" "key": "U2FsdGVkX18Zsq52FPbB7FS1QM7CwWh+t1Esi96gUMFILJD0vZXM69uQBC4sP7TjLVOiJPZt0KIq17iql+hXehLCVSbCEhwKKm+zNab+u2Swah63A52hz0KMJXddLRZ6vanmP2goLTzY7749HsFVRDq+3bdbi8fdUZCpAsSTEiqC2cRTpF5WVh0SHt1wX/i42YSQgOYPeXQtg+lRx/URk4p7lYSVQOYWXglp99kO+Va1ADoNcTgrfz3aaAxAkj0S"
} }
} }
-28
View File
@@ -47,7 +47,6 @@ type UpdaterConfig struct {
} }
type LicenseConfig struct { type LicenseConfig struct {
CryptoKey string `json:"crypto_key"` // stored as ENC(...)
Key string `json:"key"` // stored ciphertext U2FsdGVk... Key string `json:"key"` // stored ciphertext U2FsdGVk...
} }
@@ -61,7 +60,6 @@ type AppConfig struct {
DecryptedDBPassword string `json:"-"` DecryptedDBPassword string `json:"-"`
DecryptedUpdaterToken string `json:"-"` DecryptedUpdaterToken string `json:"-"`
DecryptedAdminPassword string `json:"-"` DecryptedAdminPassword string `json:"-"`
DecryptedCryptoKey string `json:"-"`
} }
var ( var (
@@ -162,7 +160,6 @@ func DefaultConfig() *AppConfig {
CurrentVersion: "v1.0.2", CurrentVersion: "v1.0.2",
}, },
License: LicenseConfig{ License: LicenseConfig{
CryptoKey: "BAD5A05F330D5B8CDDFCA9E7ECDC840B",
Key: "", Key: "",
}, },
} }
@@ -182,18 +179,15 @@ func LoadConfig(path string) (*AppConfig, error) {
cfg.DecryptedAdminPassword = cfg.Server.AdminPassword cfg.DecryptedAdminPassword = cfg.Server.AdminPassword
cfg.DecryptedDBPassword = cfg.Database.Password cfg.DecryptedDBPassword = cfg.Database.Password
cfg.DecryptedUpdaterToken = cfg.Updater.Token cfg.DecryptedUpdaterToken = cfg.Updater.Token
cfg.DecryptedCryptoKey = cfg.License.CryptoKey
// Encrypt for disk save // Encrypt for disk save
encAdmin, _ := Encrypt(cfg.Server.AdminPassword) encAdmin, _ := Encrypt(cfg.Server.AdminPassword)
encDB, _ := Encrypt(cfg.Database.Password) encDB, _ := Encrypt(cfg.Database.Password)
encTok, _ := Encrypt(cfg.Updater.Token) encTok, _ := Encrypt(cfg.Updater.Token)
encCrypto, _ := Encrypt(cfg.License.CryptoKey)
cfg.Server.AdminPassword = encAdmin cfg.Server.AdminPassword = encAdmin
cfg.Database.Password = encDB cfg.Database.Password = encDB
cfg.Updater.Token = encTok cfg.Updater.Token = encTok
cfg.License.CryptoKey = encCrypto
data, _ := json.MarshalIndent(cfg, "", " ") data, _ := json.MarshalIndent(cfg, "", " ")
_ = os.MkdirAll(filepath.Dir(configPath), 0755) _ = os.MkdirAll(filepath.Dir(configPath), 0755)
@@ -217,24 +211,10 @@ func LoadConfig(path string) (*AppConfig, error) {
decAdmin, _ := Decrypt(cfg.Server.AdminPassword) decAdmin, _ := Decrypt(cfg.Server.AdminPassword)
decDB, _ := Decrypt(cfg.Database.Password) decDB, _ := Decrypt(cfg.Database.Password)
decTok, _ := Decrypt(cfg.Updater.Token) decTok, _ := Decrypt(cfg.Updater.Token)
if cfg.License.CryptoKey == "" {
cfg.License.CryptoKey = "BAD5A05F330D5B8CDDFCA9E7ECDC840B"
}
decCrypto, _ := Decrypt(cfg.License.CryptoKey)
if decCrypto == "" {
decCrypto = "BAD5A05F330D5B8CDDFCA9E7ECDC840B"
}
cfg.DecryptedAdminPassword = decAdmin cfg.DecryptedAdminPassword = decAdmin
cfg.DecryptedDBPassword = decDB cfg.DecryptedDBPassword = decDB
cfg.DecryptedUpdaterToken = decTok cfg.DecryptedUpdaterToken = decTok
cfg.DecryptedCryptoKey = decCrypto
// Ensure crypto key is stored encrypted on disk
if !strings.HasPrefix(cfg.License.CryptoKey, "ENC(") {
encCrypto, _ := Encrypt(cfg.License.CryptoKey)
cfg.License.CryptoKey = encCrypto
}
// Auto-migrate legacy owner & repo // Auto-migrate legacy owner & repo
if cfg.Updater.Owner == "" || cfg.Updater.Owner == "hdhiswork" { if cfg.Updater.Owner == "" || cfg.Updater.Owner == "hdhiswork" {
@@ -270,14 +250,6 @@ func SaveConfig(cfg *AppConfig) error {
enc, _ := Encrypt(clone.Updater.Token) enc, _ := Encrypt(clone.Updater.Token)
clone.Updater.Token = enc clone.Updater.Token = enc
} }
if clone.License.CryptoKey == "" {
clone.License.CryptoKey = "BAD5A05F330D5B8CDDFCA9E7ECDC840B"
}
if !strings.HasPrefix(clone.License.CryptoKey, "ENC(") {
clone.DecryptedCryptoKey = clone.License.CryptoKey
enc, _ := Encrypt(clone.License.CryptoKey)
clone.License.CryptoKey = enc
}
data, err := json.MarshalIndent(&clone, "", " ") data, err := json.MarshalIndent(&clone, "", " ")
if err != nil { if err != nil {
+3 -4
View File
@@ -106,14 +106,13 @@ Mở tệp `config.json` bằng Notepad, VSCode hoặc `nano`:
} }
], ],
"license": { "license": {
"crypto_key": "ENC(U2FsdGVkX18...)", "key": "U2FsdGVkX18Zsq52FPbB7FS1QM7CwWh+t1Esi96gUMFILJD0vZXM69uQBC4sP7TjLVOiJPZt0KIq17iql+hXehLCVSbCEhwKKm+zNab+u2Swah63A52hz0KMJXddLRZ6vanmP2goLTzY7749HsFVRDq+3bdbi8fdUZCpAsSTEiqC2cRTpF5WVh0SHt1wX/i42YSQgOYPeXQtg+lRx/URk4p7lYSVQOYWXglp99kO+Va1ADoNcTgrfz3aaAxAkj0S"
"active_key": "U2FsdGVkX18Zsq52FPbB7FS1QM7CwWh+t1Esi96gUMFILJD0vZXM69uQBC4sP7TjLVOiJPZt0KIq17iql+hXehLCVSbCEhwKKm+zNab+u2Swah63A52hz0KMJXddLRZ6vanmP2goLTzY7749HsFVRDq+3bdbi8fdUZCpAsSTEiqC2cRTpF5WVh0SHt1wX/i42YSQgOYPeXQtg+lRx/URk4p7lYSVQOYWXglp99kO+Va1ADoNcTgrfz3aaAxAkj0S"
} }
} }
``` ```
> [!NOTE] > [!NOTE]
> - Mật khẩu CSDL (`password`) và Passphrase giải mã (`crypto_key`) được hỗ trợ lưu dạng mã hóa `ENC(...)`. Nếu nhập dạng chuỗi thông thường (Plaintext), hệ thống vẫn tự động nhận diện và kết nối an toàn. > - Mật khẩu CSDL (`password`) được hỗ trợ lưu dạng mã hóa `ENC(...)`. Nếu nhập dạng chuỗi thông thường (Plaintext), hệ thống vẫn tự động nhận diện và kết nối an toàn.
> - Có thể thêm bao nhiêu CSDL tùy ý vào mảng `databases`. Người dùng có thể chuyển đổi linh hoạt ngay trên dropdown của thanh công cụ Web. > - Có thể thêm bao nhiêu CSDL tùy ý vào mảng `databases`. Người dùng có thể chuyển đổi linh hoạt ngay trên dropdown của thanh công cụ Web.
--- ---
@@ -123,7 +122,7 @@ Mở tệp `config.json` bằng Notepad, VSCode hoặc `nano`:
### 5.1 Quy cách và Cấu trúc Bản quyền ### 5.1 Quy cách và Cấu trúc Bản quyền
Hệ thống sử dụng cơ chế bảo mật theo tiêu chuẩn thư viện `OTH.Adapter / HisPrivateCode`: Hệ thống sử dụng cơ chế bảo mật theo tiêu chuẩn thư viện `OTH.Adapter / HisPrivateCode`:
- **Thuật toán**: OpenSSL EVP_BytesToKey (MD5 key derivation + AES-256-CBC). - **Thuật toán**: OpenSSL EVP_BytesToKey (MD5 key derivation + AES-256-CBC).
- **Khóa dẫn xuất nội bộ**: `BAD5A05F330D5B8CDDFCA9E7ECDC840B`. - **Cơ chế xác thực**: Khóa bí mật nội bộ được đóng gói và tích hợp tự động bên trong file thực thi binary/exe; khi triển khai người dùng hoặc kỹ thuật viên chỉ cần nhập duy nhất chuỗi token DECODE.
- **Cấu trúc dữ liệu JSON sau giải mã**: - **Cấu trúc dữ liệu JSON sau giải mã**:
```json ```json
{ {
+1 -12
View File
@@ -30,9 +30,6 @@ func HandleGetAdminConfig(w http.ResponseWriter, r *http.Request) {
if respCfg.Updater.Token != "" { if respCfg.Updater.Token != "" {
respCfg.Updater.Token = "••••••••" respCfg.Updater.Token = "••••••••"
} }
if respCfg.License.CryptoKey != "" {
respCfg.License.CryptoKey = "••••••••"
}
_ = json.NewEncoder(w).Encode(map[string]interface{}{ _ = json.NewEncoder(w).Encode(map[string]interface{}{
"success": true, "success": true,
@@ -83,15 +80,7 @@ func HandleSaveAdminConfig(w http.ResponseWriter, r *http.Request) {
input.Updater.Token = enc input.Updater.Token = enc
} }
// Handle License Crypto Key // Handle License Key
if input.License.CryptoKey == "" || input.License.CryptoKey == "••••••••" {
input.License.CryptoKey = current.License.CryptoKey
input.DecryptedCryptoKey = current.DecryptedCryptoKey
} else {
input.DecryptedCryptoKey = input.License.CryptoKey
enc, _ := config.Encrypt(input.License.CryptoKey)
input.License.CryptoKey = enc
}
if input.License.Key == "" { if input.License.Key == "" {
input.License.Key = current.License.Key input.License.Key = current.License.Key
} }
+14 -11
View File
@@ -114,13 +114,22 @@ func OpenSSLDecrypt(encryptedBase64 string, passphrase string) (string, error) {
return string(plaintext[:len(plaintext)-padLen]), nil return string(plaintext[:len(plaintext)-padLen]), nil
} }
// internalSecretKey returns the internal private passphrase (DHCryptoJSKEY)
func internalSecretKey() string {
part1 := []byte{0x42, 0x41, 0x44, 0x35, 0x41, 0x30, 0x35, 0x46}
part2 := []byte{0x33, 0x33, 0x30, 0x44, 0x35, 0x42, 0x38, 0x43}
part3 := []byte{0x44, 0x44, 0x46, 0x43, 0x41, 0x39, 0x45, 0x37}
part4 := []byte{0x45, 0x43, 0x44, 0x43, 0x38, 0x34, 0x30, 0x42}
return string(append(append(append(part1, part2...), part3...), part4...))
}
// ValidateAndParseLicense parses and evaluates the license ciphertext // ValidateAndParseLicense parses and evaluates the license ciphertext
func ValidateAndParseLicense(encryptedKey string, cryptoKey string) (*LicenseInfo, error) { func ValidateAndParseLicense(encryptedKey string, cryptoKey string) (*LicenseInfo, error) {
if strings.TrimSpace(encryptedKey) == "" { if strings.TrimSpace(encryptedKey) == "" {
return nil, errors.New("chuỗi key bản quyền trống") return nil, errors.New("chuỗi key bản quyền trống")
} }
if strings.TrimSpace(cryptoKey) == "" { if strings.TrimSpace(cryptoKey) == "" {
cryptoKey = "BAD5A05F330D5B8CDDFCA9E7ECDC840B" cryptoKey = internalSecretKey()
} }
decryptedJSON, err := OpenSSLDecrypt(encryptedKey, cryptoKey) decryptedJSON, err := OpenSSLDecrypt(encryptedKey, cryptoKey)
@@ -181,11 +190,6 @@ func InitLicense() {
return return
} }
cryptoKey := cfg.DecryptedCryptoKey
if cryptoKey == "" {
cryptoKey = "BAD5A05F330D5B8CDDFCA9E7ECDC840B"
}
if cfg.License.Key == "" { if cfg.License.Key == "" {
currentLicense = &LicenseInfo{ currentLicense = &LicenseInfo{
IsValid: false, IsValid: false,
@@ -195,7 +199,7 @@ func InitLicense() {
return return
} }
info, err := ValidateAndParseLicense(cfg.License.Key, cryptoKey) info, err := ValidateAndParseLicense(cfg.License.Key, "")
if err != nil { if err != nil {
currentLicense = &LicenseInfo{ currentLicense = &LicenseInfo{
IsValid: false, IsValid: false,
@@ -231,12 +235,11 @@ func GetStatus() *LicenseInfo {
// Activate saves and applies a new license key // Activate saves and applies a new license key
func Activate(encryptedKey string) (*LicenseInfo, error) { func Activate(encryptedKey string) (*LicenseInfo, error) {
cfg := config.Get() cfg := config.Get()
cryptoKey := cfg.DecryptedCryptoKey if cfg == nil {
if cryptoKey == "" { return nil, errors.New("cấu hình chưa được khởi tạo")
cryptoKey = "BAD5A05F330D5B8CDDFCA9E7ECDC840B"
} }
info, err := ValidateAndParseLicense(encryptedKey, cryptoKey) info, err := ValidateAndParseLicense(encryptedKey, "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
+2 -2
View File
@@ -6,9 +6,9 @@ import (
func TestOpenSSLDecryptUserKey(t *testing.T) { func TestOpenSSLDecryptUserKey(t *testing.T) {
cipherText := "U2FsdGVkX18Zsq52FPbB7FS1QM7CwWh+t1Esi96gUMFILJD0vZXM69uQBC4sP7TjLVOiJPZt0KIq17iql+hXehLCVSbCEhwKKm+zNab+u2Swah63A52hz0KMJXddLRZ6vanmP2goLTzY7749HsFVRDq+3bdbi8fdUZCpAsSTEiqC2cRTpF5WVh0SHt1wX/i42YSQgOYPeXQtg+lRx/URk4p7lYSVQOYWXglp99kO+Va1ADoNcTgrfz3aaAxAkj0S" cipherText := "U2FsdGVkX18Zsq52FPbB7FS1QM7CwWh+t1Esi96gUMFILJD0vZXM69uQBC4sP7TjLVOiJPZt0KIq17iql+hXehLCVSbCEhwKKm+zNab+u2Swah63A52hz0KMJXddLRZ6vanmP2goLTzY7749HsFVRDq+3bdbi8fdUZCpAsSTEiqC2cRTpF5WVh0SHt1wX/i42YSQgOYPeXQtg+lRx/URk4p7lYSVQOYWXglp99kO+Va1ADoNcTgrfz3aaAxAkj0S"
passphrase := "BAD5A05F330D5B8CDDFCA9E7ECDC840B"
info, err := ValidateAndParseLicense(cipherText, passphrase) // Passing empty cryptoKey triggers internalSecretKey() automatically
info, err := ValidateAndParseLicense(cipherText, "")
if err != nil { if err != nil {
t.Fatalf("Validation failed: %v", err) t.Fatalf("Validation failed: %v", err)
} }