为什么此 Java NaCl 加密不适用于 GitHub Actions Secrets
Why does this Java NaCl encryption not work with GitHub Actions Secrets
我正在尝试编写一个 Java 应用程序,它在 GitHub 存储库中创建秘密以供 GitHub 操作使用。有许多可用的 SodiumLib 包装器,但它们通常包装本机 C 库。我一直在寻找一个纯粹的 Java 实现。
https://github.com/NeilMadden/salty-coffee 似乎是我需要的,而且库确实会创建加密字符串。下面的 Groovy 脚本采用密钥和输入值,并生成加密值:
@Grab(group='software.pando.crypto', module='salty-coffee', version='1.0.4')
@Grab(group='org.apache.commons', module='commons-lang3', version='3.12.0')
@Grab(group='commons-codec', module='commons-codec', version='1.15')
import software.pando.crypto.nacl.*
import org.apache.commons.lang3.*
import java.util.*
import org.apache.commons.codec.binary.Base64
import java.security.*
import java.nio.charset.*
def base64 = new Base64(3)
def key = base64.decode(args[0])
def value = StringUtils.defaultIfEmpty(args[1], "")
println "Encrypting " + value
def keyPair = CryptoBox.keyPair();
def githubPublicKey = CryptoBox.publicKey(key)
def box = CryptoBox.encrypt(keyPair.getPrivate(), githubPublicKey, value)
def out = new ByteArrayOutputStream()
box.writeTo(out);
out.flush();
def encryptedValue = new String(base64.encode(out.toByteArray()))
println encryptedValue
例如:
groovy encrypt.groovy 2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU= test
问题是生成的值在用于创建新值时被忽略 GitHub API secret。您尝试创建密钥,并且 HTTP 请求工作正常,但是当您在工作流中使用它时密钥为空。
从这个生成加密值的 Python 脚本创建的秘密工作正常,所以我知道我正在进行正确的 HTTP 调用并使用正确的密钥生成 GitHub 秘密:
from base64 import b64encode
from nacl import encoding, public
import sys
def encrypt(public_key: str, secret_value: str) -> str:
"""Encrypt a Unicode string using the public key."""
public_key = public.PublicKey(public_key.encode("utf-8"), encoding.Base64Encoder())
sealed_box = public.SealedBox(public_key)
encrypted = sealed_box.encrypt(secret_value.encode("utf-8"))
return b64encode(encrypted).decode("utf-8")
print(encrypt(sys.argv[1], sys.argv[2]))
我在 Java(或 Groovy)示例中做错了什么?
Python代码使用了sealed boxes,Java/Groovy代码没有,所以两者不兼容。
由于生成的密文不确定,因此无法进行直接比较。一个合理的测试是使用相同的代码来解密两个代码的密文。
以下Python代码使用贴出的代码进行加密,然后用补码进行解密。此代码稍后将用于测试Java代码:
from base64 import b64encode, b64decode
from nacl import encoding, public
def encrypt(public_key: str, secret_value: str) -> str:
"""Encrypt a Unicode string using the public key."""
public_key = public.PublicKey(public_key.encode("utf-8"), encoding.Base64Encoder())
sealed_box = public.SealedBox(public_key)
encrypted = sealed_box.encrypt(secret_value.encode("utf-8"))
return b64encode(encrypted).decode("utf-8")
pkB64 = 'xBC9lTyWdE/6EObv5NjryMbIvrviOzzPA+5XyM0QcHE='
skB64 = '0b9867Pq6sEdnxYM1ZscOhiMpruKn1Xg3xxB+wUF5eI='
# Encryption
encrypted = encrypt(pkB64, 'test')
# Decryption
secret_key = public.PrivateKey(skB64.encode("utf-8"), encoding.Base64Encoder())
unseal_box = public.SealedBox(secret_key)
plaintext = unseal_box.decrypt(b64decode(encrypted))
print(plaintext.decode('utf-8')) # test
salty-coffee (at least I haven't found a way). Therefore, and because I don't know of any pure Java library that supports sealed boxes, I use lazysodium 似乎不支持密封盒(尽管它也是 Libsodium 库的包装器)来演示迁移。对于其他库(即使是纯 Java 库,如果有的话),这应该大致类似:
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HexFormat;
import com.goterl.lazysodium.LazySodiumJava;
import com.goterl.lazysodium.SodiumJava;
import com.goterl.lazysodium.utils.Key;
import com.goterl.lazysodium.utils.KeyPair;
....
SodiumJava sodium = new SodiumJava();
LazySodiumJava lazySodium = new LazySodiumJava(sodium, StandardCharsets.UTF_8);
Key secretKey = Key.fromBase64String("0b9867Pq6sEdnxYM1ZscOhiMpruKn1Xg3xxB+wUF5eI=");
Key publicKey = Key.fromBase64String("xBC9lTyWdE/6EObv5NjryMbIvrviOzzPA+5XyM0QcHE=");
// Encryption
KeyPair keyPair = new KeyPair(publicKey, secretKey);
String ciphertext = lazySodium.cryptoBoxSealEasy("test", publicKey);
System.out.println(Base64.getEncoder().encodeToString(HexFormat.of().parseHex(ciphertext)));
// Decryption
String decrypted = lazySodium.cryptoBoxSealOpenEasy(ciphertext, keyPair);
System.out.println(decrypted);
如果用这段代码生成的密文作为Python代码中的密文,则可以成功解密,说明两种代码的加密功能完全相同。
编辑:
作为另一个库的替代品,salty-coffee 可以扩展以支持密封盒。
如果寄件人使用密封箱,基本上会出现以下情况:
- 首先,生成一个临时密钥对:
ephemSK
、ephemPK
。
- 设
PK
为接收者的public键。一个24字节的nonce是这样确定的:nonce = Blake2b(ephemPK || PK)
- 使用 CryptoBox 执行加密,使用
ephemSK
作为密钥,PK
作为 public 密钥和先前生成的 nonce
.
CryptoBox
returns密文与16字节的拼接MAC。 ephemPK
被添加到密文前面。这3部分拼接起来就是封箱的结果。
salty-coffee 提供除 Blake2b 之外的所有 Libsodium 功能。为此,您可以使用例如充气城堡.
一个可能的实现是:
import software.pando.crypto.nacl.*;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.util.Base64;
import org.bouncycastle.crypto.digests.Blake2bDigest;
...
byte[] plaintext = "The quick brown fox jumps over the lazy dog".getBytes(StandardCharsets.UTF_8);
// Sender's secret key SK, receiver's public key PK
byte[] SK = Base64.getDecoder().decode("0b9867Pq6sEdnxYM1ZscOhiMpruKn1Xg3xxB+wUF5eI=");
byte[] PK = Base64.getDecoder().decode("xBC9lTyWdE/6EObv5NjryMbIvrviOzzPA+5XyM0QcHE=");
// Create an ephemeral keypair: ephemSK, ephemPK
KeyPair ephemKeyPair = CryptoBox.keyPair();
byte[] ephemSK_pkcs8 = ephemKeyPair.getPrivate().getEncoded();
byte[] ephemPK_x509 = ephemKeyPair.getPublic().getEncoded();
byte[] ephemSK = getRawKey(ephemSK_pkcs8);
byte[] ephemPK = getRawKey(ephemPK_x509);
// Create the nonce = Blake2b(ephemeralPK || PK))
byte[] noncematerial = new byte[64];
System.arraycopy(ephemPK, 0, noncematerial, 0, ephemPK.length);
System.arraycopy(PK, 0, noncematerial, ephemPK.length, PK.length);
byte[] nonce = new byte[24];
Blake2bDigest dig = new Blake2bDigest(null, nonce.length, null, null);
dig.update(noncematerial, 0, noncematerial.length);
dig.doFinal(nonce, 0);
// Encrypt with CryptoBox using ephemSK, PK and the nonce
CryptoBox cryptobox = CryptoBox.encrypt(CryptoBox.privateKey(ephemSK), CryptoBox.publicKey(PK), nonce, plaintext);
byte[] ciphertextMAC = cryptobox.getCiphertextWithTag();
// Prepend ephemPK
byte[] secretBoxSealed = new byte[ephemPK.length + ciphertextMAC.length];
System.arraycopy(ephemPK, 0, secretBoxSealed, 0, ephemPK.length);
System.arraycopy(ciphertextMAC, 0, secretBoxSealed, ephemPK.length, ciphertextMAC.length);
String secretBoxSealedB64 = Base64.getEncoder().encodeToString(secretBoxSealed);
System.out.println(secretBoxSealedB64);
与:
// The raw keys are the last 32 bytes in PKCS#8 and X.509 formatted keys respectively.
private static byte[] getRawKey(byte[] key) {
byte[] result = new byte[32];
System.arraycopy(key, key.length - result.length, result, 0, result.length);
return result;
}
用上面的Python代码可以成功解密用该代码创建的密文,证明兼容性。
我正在尝试编写一个 Java 应用程序,它在 GitHub 存储库中创建秘密以供 GitHub 操作使用。有许多可用的 SodiumLib 包装器,但它们通常包装本机 C 库。我一直在寻找一个纯粹的 Java 实现。
https://github.com/NeilMadden/salty-coffee 似乎是我需要的,而且库确实会创建加密字符串。下面的 Groovy 脚本采用密钥和输入值,并生成加密值:
@Grab(group='software.pando.crypto', module='salty-coffee', version='1.0.4')
@Grab(group='org.apache.commons', module='commons-lang3', version='3.12.0')
@Grab(group='commons-codec', module='commons-codec', version='1.15')
import software.pando.crypto.nacl.*
import org.apache.commons.lang3.*
import java.util.*
import org.apache.commons.codec.binary.Base64
import java.security.*
import java.nio.charset.*
def base64 = new Base64(3)
def key = base64.decode(args[0])
def value = StringUtils.defaultIfEmpty(args[1], "")
println "Encrypting " + value
def keyPair = CryptoBox.keyPair();
def githubPublicKey = CryptoBox.publicKey(key)
def box = CryptoBox.encrypt(keyPair.getPrivate(), githubPublicKey, value)
def out = new ByteArrayOutputStream()
box.writeTo(out);
out.flush();
def encryptedValue = new String(base64.encode(out.toByteArray()))
println encryptedValue
例如:
groovy encrypt.groovy 2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU= test
问题是生成的值在用于创建新值时被忽略 GitHub API secret。您尝试创建密钥,并且 HTTP 请求工作正常,但是当您在工作流中使用它时密钥为空。
从这个生成加密值的 Python 脚本创建的秘密工作正常,所以我知道我正在进行正确的 HTTP 调用并使用正确的密钥生成 GitHub 秘密:
from base64 import b64encode
from nacl import encoding, public
import sys
def encrypt(public_key: str, secret_value: str) -> str:
"""Encrypt a Unicode string using the public key."""
public_key = public.PublicKey(public_key.encode("utf-8"), encoding.Base64Encoder())
sealed_box = public.SealedBox(public_key)
encrypted = sealed_box.encrypt(secret_value.encode("utf-8"))
return b64encode(encrypted).decode("utf-8")
print(encrypt(sys.argv[1], sys.argv[2]))
我在 Java(或 Groovy)示例中做错了什么?
Python代码使用了sealed boxes,Java/Groovy代码没有,所以两者不兼容。
由于生成的密文不确定,因此无法进行直接比较。一个合理的测试是使用相同的代码来解密两个代码的密文。
以下Python代码使用贴出的代码进行加密,然后用补码进行解密。此代码稍后将用于测试Java代码:
from base64 import b64encode, b64decode
from nacl import encoding, public
def encrypt(public_key: str, secret_value: str) -> str:
"""Encrypt a Unicode string using the public key."""
public_key = public.PublicKey(public_key.encode("utf-8"), encoding.Base64Encoder())
sealed_box = public.SealedBox(public_key)
encrypted = sealed_box.encrypt(secret_value.encode("utf-8"))
return b64encode(encrypted).decode("utf-8")
pkB64 = 'xBC9lTyWdE/6EObv5NjryMbIvrviOzzPA+5XyM0QcHE='
skB64 = '0b9867Pq6sEdnxYM1ZscOhiMpruKn1Xg3xxB+wUF5eI='
# Encryption
encrypted = encrypt(pkB64, 'test')
# Decryption
secret_key = public.PrivateKey(skB64.encode("utf-8"), encoding.Base64Encoder())
unseal_box = public.SealedBox(secret_key)
plaintext = unseal_box.decrypt(b64decode(encrypted))
print(plaintext.decode('utf-8')) # test
salty-coffee (at least I haven't found a way). Therefore, and because I don't know of any pure Java library that supports sealed boxes, I use lazysodium 似乎不支持密封盒(尽管它也是 Libsodium 库的包装器)来演示迁移。对于其他库(即使是纯 Java 库,如果有的话),这应该大致类似:
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HexFormat;
import com.goterl.lazysodium.LazySodiumJava;
import com.goterl.lazysodium.SodiumJava;
import com.goterl.lazysodium.utils.Key;
import com.goterl.lazysodium.utils.KeyPair;
....
SodiumJava sodium = new SodiumJava();
LazySodiumJava lazySodium = new LazySodiumJava(sodium, StandardCharsets.UTF_8);
Key secretKey = Key.fromBase64String("0b9867Pq6sEdnxYM1ZscOhiMpruKn1Xg3xxB+wUF5eI=");
Key publicKey = Key.fromBase64String("xBC9lTyWdE/6EObv5NjryMbIvrviOzzPA+5XyM0QcHE=");
// Encryption
KeyPair keyPair = new KeyPair(publicKey, secretKey);
String ciphertext = lazySodium.cryptoBoxSealEasy("test", publicKey);
System.out.println(Base64.getEncoder().encodeToString(HexFormat.of().parseHex(ciphertext)));
// Decryption
String decrypted = lazySodium.cryptoBoxSealOpenEasy(ciphertext, keyPair);
System.out.println(decrypted);
如果用这段代码生成的密文作为Python代码中的密文,则可以成功解密,说明两种代码的加密功能完全相同。
编辑:
作为另一个库的替代品,salty-coffee 可以扩展以支持密封盒。
如果寄件人使用密封箱,基本上会出现以下情况:
- 首先,生成一个临时密钥对:
ephemSK
、ephemPK
。 - 设
PK
为接收者的public键。一个24字节的nonce是这样确定的:nonce = Blake2b(ephemPK || PK)
- 使用 CryptoBox 执行加密,使用
ephemSK
作为密钥,PK
作为 public 密钥和先前生成的nonce
. CryptoBox
returns密文与16字节的拼接MAC。ephemPK
被添加到密文前面。这3部分拼接起来就是封箱的结果。
salty-coffee 提供除 Blake2b 之外的所有 Libsodium 功能。为此,您可以使用例如充气城堡.
一个可能的实现是:
import software.pando.crypto.nacl.*;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.util.Base64;
import org.bouncycastle.crypto.digests.Blake2bDigest;
...
byte[] plaintext = "The quick brown fox jumps over the lazy dog".getBytes(StandardCharsets.UTF_8);
// Sender's secret key SK, receiver's public key PK
byte[] SK = Base64.getDecoder().decode("0b9867Pq6sEdnxYM1ZscOhiMpruKn1Xg3xxB+wUF5eI=");
byte[] PK = Base64.getDecoder().decode("xBC9lTyWdE/6EObv5NjryMbIvrviOzzPA+5XyM0QcHE=");
// Create an ephemeral keypair: ephemSK, ephemPK
KeyPair ephemKeyPair = CryptoBox.keyPair();
byte[] ephemSK_pkcs8 = ephemKeyPair.getPrivate().getEncoded();
byte[] ephemPK_x509 = ephemKeyPair.getPublic().getEncoded();
byte[] ephemSK = getRawKey(ephemSK_pkcs8);
byte[] ephemPK = getRawKey(ephemPK_x509);
// Create the nonce = Blake2b(ephemeralPK || PK))
byte[] noncematerial = new byte[64];
System.arraycopy(ephemPK, 0, noncematerial, 0, ephemPK.length);
System.arraycopy(PK, 0, noncematerial, ephemPK.length, PK.length);
byte[] nonce = new byte[24];
Blake2bDigest dig = new Blake2bDigest(null, nonce.length, null, null);
dig.update(noncematerial, 0, noncematerial.length);
dig.doFinal(nonce, 0);
// Encrypt with CryptoBox using ephemSK, PK and the nonce
CryptoBox cryptobox = CryptoBox.encrypt(CryptoBox.privateKey(ephemSK), CryptoBox.publicKey(PK), nonce, plaintext);
byte[] ciphertextMAC = cryptobox.getCiphertextWithTag();
// Prepend ephemPK
byte[] secretBoxSealed = new byte[ephemPK.length + ciphertextMAC.length];
System.arraycopy(ephemPK, 0, secretBoxSealed, 0, ephemPK.length);
System.arraycopy(ciphertextMAC, 0, secretBoxSealed, ephemPK.length, ciphertextMAC.length);
String secretBoxSealedB64 = Base64.getEncoder().encodeToString(secretBoxSealed);
System.out.println(secretBoxSealedB64);
与:
// The raw keys are the last 32 bytes in PKCS#8 and X.509 formatted keys respectively.
private static byte[] getRawKey(byte[] key) {
byte[] result = new byte[32];
System.arraycopy(key, key.length - result.length, result, 0, result.length);
return result;
}
用上面的Python代码可以成功解密用该代码创建的密文,证明兼容性。