草庐IT

php - 使用golang解密用php openssl_encrypt加密的文件

coder 2024-07-05 原文

首先。我在这里如履薄冰!

我有一个从 php 获得的加密文件。我正在尝试用 golang 解密它。

php 应用程序使用公共(public) RSA key 来加密用于使用 aes-256-cbc 加密的 key 。

我已经创建了一些概念验证代码,但我做不对。尽管 key 和 iv 在两边看起来都是正确的,但也有不正确的地方。结果只是垃圾。我怀疑某些编码不匹配(期望 base64,获取字符串字节......某事)或者我误解了一些概念。

加密:

<?php

$cipher = "AES-256-CBC";
$ivLength = openssl_cipher_iv_length($cipher="AES-256-CBC");
echo "iv len: " . $ivLength . "\n";
$iv = openssl_random_pseudo_bytes($ivLength);
$key = "1234567890abcdef";

$ciphertext = openssl_encrypt("hello world", $cipher, $key, 0, $iv);

$publicKey = openssl_pkey_get_public(file_get_contents("some-public-key.pub"));
if (!$publicKey) {
   die("OpenSSL: Unable to get public key for encryption. Is the location correct? Does this key require a password?");
}

$ok = openssl_public_encrypt($key, $encryptedKey, $publicKey);
if (!$ok) {
    die("Encryption failed. Ensure you are using a PUBLIC key.");
}

echo "key unencrypted: " . $key . "\n";
echo "iv: " . base64_encode($iv) . "\n";
echo "ciphertext: " . $ciphertext . "\n";
echo "ciphertext binary: " . (base64_decode($ciphertext)) . "\n";
echo "combined: " . ($iv . $ciphertext) . "\n";

file_put_contents("key.enc", $encryptedKey);
file_put_contents("content.enc", $iv . $ciphertext);
file_put_contents("content.dec", openssl_decrypt($ciphertext, $cipher, $key, 0, $iv));

openssl_free_key($publicKey);
?>

解密:

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "crypto/rsa"
    "crypto/x509"
    "encoding/base64"
    "encoding/hex"
    "encoding/pem"
    "fmt"
    "io"
    "io/ioutil"
    "log"
)

func main() {

    // Read the input file
    in, err := ioutil.ReadFile("key.enc")
    if err != nil {
        log.Fatalf("input file: %s", err)
    }

    // Read the private key
    pemData, err := ioutil.ReadFile("some-private-key")
    if err != nil {
        log.Fatalf("read key file: %s", err)
    }

    // Extract the PEM-encoded data block
    block, _ := pem.Decode(pemData)
    if block == nil {
        log.Fatalf("bad key data: %s", "not PEM-encoded")
    }
    if got, want := block.Type, "RSA PRIVATE KEY"; got != want {
        log.Fatalf("unknown key type %q, want %q", got, want)
    }

    // Decode the RSA private key
    priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
    if err != nil {
        log.Fatalf("bad private key: %s", err)
    }

    // Decrypt the data
    cipherKey, err := rsa.DecryptPKCS1v15(rand.Reader, priv, in)
    if err != nil {
        log.Fatalf("decrypt: %s", err)
    }

    fmt.Println("Key decrypted:", string(cipherKey))

    // Read encrypted content file
    content, err := ioutil.ReadFile("content.enc")
    if err != nil {
        log.Fatalf("input file: %s", err)
    }

    fmt.Println("Cipherkey: ", string(cipherKey))
    cipherText := content

    cipherBlock, err := aes.NewCipher(cipherKey)
    if err != nil {
        panic(err)
    }

    iv := cipherText[:aes.BlockSize]
    fmt.Println("iv:", base64.StdEncoding.EncodeToString(iv))
    fmt.Println("ciphertext:", string(cipherText[aes.BlockSize:]))
    cipherText, _ = base64.StdEncoding.DecodeString(string(cipherText[aes.BlockSize:]))
    fmt.Println("ciphertext binary: ", string(cipherText))

    // CBC mode always works in whole blocks.
    if len(cipherText)%aes.BlockSize != 0 {
        panic(fmt.Sprintf("ciphertext (len=%d) is not a multiple of the block size (%d)", len(cipherText), aes.BlockSize))
    }

    mode := cipher.NewCBCDecrypter(cipherBlock, iv)
    mode.CryptBlocks(cipherText, cipherText)

    fmt.Printf("The result: %s\n", cipherText)
}

这是执行此操作的一些示例输出(首先是 php,然后是 go):

iv len: 16
key unencrypted: 1234567890abcdef
iv: QffXbVRuwyopwwvQXQ8N6g==
ciphertext: Wk8Gv1xQWikp1YryQiywgQ==
ciphertext binary: ZO�\PZ))Պ�B,��
combined: A��mTn�*)�
�Wk8Gv1xQWikp1YryQiywgQ==
-----
Key decrypted: 1234567890abcdef
Cipherkey:  1234567890abcdef
iv: QffXbVRuwyopwwvQXQ8N6g==
ciphertext: Wk8Gv1xQWikp1YryQiywgQ==
ciphertext binary:  ZO�\PZ))Պ�B,��
The result: ��2��J���~A�D

最佳答案

让我们退一步简化一下:

// encrypt.php
<?php

$iv = base64_decode("AJf3QItKM7+Lkh/BZT2xNg==");
$key = "1234567890abcdef";

echo openssl_encrypt("hello world", "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);


// decrypt.go
package main

import (
        "crypto/aes"
        "crypto/cipher"
        "encoding/base64"
        "fmt"
        "io/ioutil"
        "log"
        "os"
)

func main() {
        iv, _ := base64.StdEncoding.DecodeString("AJf3QItKM7+Lkh/BZT2xNg==")
        key := []byte("1234567890abcdef")

        text, _ := ioutil.ReadAll(os.Stdin)

        cipherBlock, err := aes.NewCipher(key)
        if err != nil {
            log.Fatal(err)
        }

        cipher.NewCBCDecrypter(cipherBlock, iv).CryptBlocks(text, text)
        fmt.Println(string(text))
}

如果我们运行这个,低看,我们得到垃圾:

$ php encrypt.php | go run decrypt.go 
7v>r

请注意 Go 代码中明显缺少字符串 256。您知道,不需要您指定 key 大小,而是查看 key 的大小。在这种情况下,您定义了一个 16 字节/128 位 key 。

如果您指定 AES-256,但随后将 128 位 key 传递给 openssl,openssl 会用零填充 key ,直到它达到 256 位长。

以下是可能的修复方法(按照我个人喜好的顺序):

使用 256 位 key :

--- encrypt.php.orig    2018-04-13 10:55:10.988913605 +0200
+++ encrypt.php.fix-key 2018-04-13 10:57:13.565673205 +0200
@@ -3,3 +3,3 @@
 $iv = base64_decode("AJf3QItKM7+Lkh/BZT2xNg==");
-$key = "1234567890abcdef";
+$key = "1234567890abcdef1234567890abcdef";

--- decrypt.go.orig     2018-04-13 10:55:17.083901651 +0200
+++ decrypt.go.fix-key  2018-04-13 10:55:49.467838139 +0200
@@ -14,3 +14,3 @@
        iv, _ := base64.StdEncoding.DecodeString("AJf3QItKM7+Lkh/BZT2xNg==")
-       key := []byte("1234567890abcdef")
+       key := []byte("1234567890abcdef1234567890abcdef")

在PHP中,选择与 key 匹配的加密方法:

--- encrypt.php.orig    2018-04-13 10:55:10.988913605 +0200
+++ encrypt.php.fix-method      2018-04-13 10:56:18.105781974 +0200
@@ -5,2 +5,2 @@

-echo openssl_encrypt("hello world", "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);
+echo openssl_encrypt("hello world", "AES-128-CBC", $key, OPENSSL_RAW_DATA, $iv);

在 Go 中也做零填充:

--- decrypt.go.orig     2018-04-13 10:55:17.083901651 +0200
+++ decrypt.go.pad-key  2018-04-13 10:56:39.601739816 +0200
@@ -14,3 +14,4 @@
        iv, _ := base64.StdEncoding.DecodeString("AJf3QItKM7+Lkh/BZT2xNg==")
-       key := []byte("1234567890abcdef")
+       key := make([]byte, 32)
+       copy(key, []byte("1234567890abcdef"))

关于php - 使用golang解密用php openssl_encrypt加密的文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49810592/

有关php - 使用golang解密用php openssl_encrypt加密的文件的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  7. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  8. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  9. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  10. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

随机推荐