为什么此 Java NaCl 加密不适用于 GitHub Actions Secrets

物理化学

我正在尝试编写一个 Java 应用程序,该应用程序在 GitHub 存储库中创建秘密以供 GitHub Actions 使用。有许多可用的 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 代码使用密封盒,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

密封盒似乎不支持咸咖啡(至少我还没有找到方法)。因此,并且因为我不知道任何支持密封盒的Java 库,所以我使用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 以支持密封盒。

如果发件人使用密封箱,基本上会发生以下情况:

  1. 首先,生成一个临时密钥对:ephemSK, ephemPK.
  2. PK成为接收者的公钥。一个 24 字节的 nonce 确定如下:nonce = Blake2b(ephemPK || PK)
  3. 使用CryptoBox执行加密,使用ephemSK密钥、PK公钥和先前生成的nonce.
  4. CryptoBox返回密文和 16 字节 MAC 的串联。ephemPK被附加到密文中。这3部分的串联是密封盒的结果。

salty-coffee 提供了实现所需的所有 Libsodium 功能,除了 Blake2b。为此,您可以使用例如 Bouncycastle。

一个可能的实现是:

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 代码成功解密,证明了兼容性。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

Django迁移不适用于GitHub Actions

GitHub Actions 自动批准不适用于 GitHub Actions 机器人创建的拉取请求

为什么此代码不适用于svg元素?

为什么此查询不适用于指定的日期?

为什么此过渡属性不适用于 css?

为什么Java类型推断不适用于参数化类型的返回值?

为什么Java中的toString方法似乎不适用于数组

为什么Java CRLF令牌不适用于批处理文件输入?

为什么gradle jettyRunWar任务不适用于java8?

如何在 .Renviron 文件的 GitHub Actions 工作流程中引用 GitHub Secrets

Github Gitignor不适用于OneSignalSDKWorker

为什么Diamond运算符不适用于Java 7中的java.util.Collections方法?

为什么 double equal 不适用于 for 循环条件,而小于 equals 在 Java 中有效?

为什么此解决方案适用于 Javascript 而不适用于 Python?(动态编程)

为什么此扩展方法适用于泛型而不适用于设置的基本类型?

Android App Actions不适用于语音命令

为什么此模式不适用于敲除验证?(分隔的邮政编码)

为什么使用移位的此交换宏不适用于负数?

了解此DNN模型以及为什么它不适用于多标签分类

为什么此解决方案不适用于硬币找零算法?

为什么此string。标点符号代码不适用于剥离标点符号?

为什么此日志记录设置不适用于 INFO?

为什么此未定义检查不适用于查找功能?

为什么此Dijkstra算法不适用于此特定输入?

为什么此Bootstrap复选框不适用于jQuery?

为什么此红宝石序列不适用于两位数数字?

为什么此代码不适用于特定类型的数据?

Perl:为什么此表达式不适用于新版本的Perl?

为什么此Delegate约束不适用于定义事件?