OpenNHP/opennhp · error

Failed to decrypt ztdo file

Error message

Failed to decrypt ztdo file: %v

What it means

ztdo.DecryptZtdoFile failed while decrypting the ztdo file from ztdoPath into the output path using the agreed gcmKey and ad. Failures include GCM authentication tag mismatch (wrong key/AD), unreadable input file, or unwritable output path. On success the decrypted path is recorded in decryptedZtdoRecord.

Solutions

  1. Confirm the same provider public key and cipher scheme (curve vs gmsm) used at encryption time.
  2. Verify the downloaded file is intact (its id check passed and size matches) — AEAD failures usually mean altered data or wrong key.
  3. Check output path is writable and its directory exists.
  4. Re-request a fresh ztdo copy; if it persists, have the provider re-encrypt with current keys.
  5. Align ztdolib versions so DataMessagePatterns/AD computation match.

Example fix

// before: scheme guessed from header only
saData.SetRemoteStaticPublicKey(providerPublicKey)
gcmKey, ad = saData.AgreeSymmetricKey()

// after: verify scheme matches encryption metadata
if ztdo.GetCipherScheme() != localCipherScheme {
    return "", fmt.Errorf("cipher scheme mismatch: file=%d local=%d", ztdo.GetCipherScheme(), localCipherScheme)
}
gcmKey, ad = saData.AgreeSymmetricKey()
Defensive patterns

Strategy: try-catch

Validate before calling

if fi, err := os.Stat(ztdoPath); err != nil || fi.Size() == 0 {
    return fmt.Errorf("ztdo input missing or empty")
}
if err := os.MkdirAll(filepath.Dir(output), 0o755); err != nil {
    return fmt.Errorf("output dir not writable: %w", err)
}

Type guard

func outputWritable(path string) bool { return filepath.Dir(path) != "" && unix.Access(filepath.Dir(path), unix.W_OK) == nil }

Try / catch

if err := ztdo.DecryptZtdoFile(ztdoPath, output, gcmKey[:], ad); err != nil {
    os.Remove(output) // avoid partial plaintext
    return "", fmt.Errorf("Failed to decrypt ztdo file: %v", err)
}

Prevention

When it happens

Trigger: DecryptZtdoFile(ztdoPath, output, gcmKey[:], ad) errors: AES-GCM/SM4-GCM open failure because the derived key or AD differs from encryption time, wrong ECC-mode algorithm chosen, corrupt ciphertext, or file IO error on output.

Common situations: Provider key rotated between encryption and decryption; symmetric agreement inputs (PSK, patterns, remote static key) mismatched; data altered in storage breaking the AEAD tag; output directory missing or permission denied; mixing curve and GMSM cipher schemes.

Related errors


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/55dddbd3b2ac3f0c. Report an issue: GitHub.

Appendix: source

Thrown at endpoints/agent/udpagent.go:1425

			dataKeyPairEccMode := ztdo.GetECCMode()

			dataMsgPattern := [][]ztdolib.MessagePattern{
				{ztdolib.MessagePatternS, ztdolib.MessagePatternDHSS},
				{ztdolib.MessagePatternRS, ztdolib.MessagePatternDHSS},
			}

			dataPrk, _ := base64.StdEncoding.DecodeString(dataPrkBase64)
			saData := ztdolib.NewSymmetricAgreement(dataKeyPairEccMode, false)
			saData.SetMessagePatterns(dataMsgPattern)
			saData.SetStaticKeyPair(core.ECDHFromKey(dataKeyPairEccMode.ToEccType(), dataPrk))

			providerPublicKey, _ := base64.StdEncoding.DecodeString(dataPrkWrapping.ProviderPublicKeyBase64)
			saData.SetRemoteStaticPublicKey(providerPublicKey)

			gcmKey, ad = saData.AgreeSymmetricKey()

			if err := ztdo.DecryptZtdoFile(ztdoPath, output, gcmKey[:], ad); err != nil {
				return "", fmt.Errorf("Failed to decrypt ztdo file: %v", err)
			} else {
				a.decryptedZtdoRecord[ztdoId] = output
			}
		} else {
			output = decryptedOutput
		}
	} else {
		teeNotAuthorizedCode, _ := strconv.Atoi(common.ErrTEENotAuthorized.ErrorCode())
		if dagMsg.ErrCode == teeNotAuthorizedCode {
			a.trustedByNHPDB.Store(false)
		}

		return "", fmt.Errorf("Error: fail to request ztdo with error: %s.", dagMsg.ErrMsg)
	}
	return output, nil
}

// GetFirstServerPeer returns the representative peer of an arbitrary

View on GitHub (pinned to 6e04ca5ff0)