簡體   English   中英

用C#解密用Java加密的密碼

[英]Decrypt in C# a password encrypted in Java

Openbravo軟件及其派生產品(例如unicentaopos)具有以下加密實現,可將數據庫密碼存儲在純配置文件中。

package com.openbravo.pos.util;

import java.io.UnsupportedEncodingException;
import java.security.*;
import javax.crypto.*;

/**
 *
 * @author JG uniCenta
 */
public class AltEncrypter {

    private Cipher cipherDecrypt;
    private Cipher cipherEncrypt;

    /** Creates a new instance of Encrypter
     * @param passPhrase */
    public AltEncrypter(String passPhrase) {

        try {
            SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
            sr.setSeed(passPhrase.getBytes("UTF8"));
            KeyGenerator kGen = KeyGenerator.getInstance("DESEDE");
            kGen.init(168, sr);
            Key key = kGen.generateKey();

            cipherEncrypt = Cipher.getInstance("DESEDE/ECB/PKCS5Padding");
            cipherEncrypt.init(Cipher.ENCRYPT_MODE, key);

            cipherDecrypt = Cipher.getInstance("DESEDE/ECB/PKCS5Padding");
            cipherDecrypt.init(Cipher.DECRYPT_MODE, key);
        } catch (UnsupportedEncodingException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
        }
    }

    /**
     *
     * @param str
     * @return
     */
    public String encrypt(String str) {
        try {
            return StringUtils.byte2hex(cipherEncrypt.doFinal(str.getBytes("UTF8")));
        } catch (UnsupportedEncodingException | BadPaddingException | IllegalBlockSizeException e) {
        }
        return null;
    }

    /**
     *
     * @param str
     * @return
     */
    public String decrypt(String str) {
        try {
            return new String(cipherDecrypt.doFinal(StringUtils.hex2byte(str)), "UTF8");
        } catch (UnsupportedEncodingException | BadPaddingException | IllegalBlockSizeException e) {
        }
        return null;
    }    
}

為了加密,使用以下命令(僅加密密碼):

 config.setProperty("db.user", jtxtDbUser.getText());
        AltEncrypter cypher = new AltEncrypter("cypherkey" + jtxtDbUser.getText());       
        config.setProperty("db.password", "crypt:" + cypher.encrypt(new String(jtxtDbPassword.getPassword())));

要解密,請使用以下內容:

  String sDBUser = m_App.getProperties().getProperty("db.user");
  String sDBPassword = m_App.getProperties().getProperty("db.password");
  if (sDBUser != null && sDBPassword != null && sDBPassword.startsWith("crypt:")) {
     AltEncrypter cypher = new AltEncrypter("cypherkey" + sDBUser);
     sDBPassword = cypher.decrypt(sDBPassword.substring(6));
   }

我正在使用C#開發一個獨立的軟件模塊,我想從該配置文件中讀取數據庫密碼。 有關如何完成此操作的任何建議?

通過分析代碼,我可以得出以下結論:

  1. 密碼“ encryption”是可逆的,因為以后在軟件中使用它來建立數據庫連接字符串。
  2. 基本密碼是“密碼” +用戶名
  3. 密碼以以下格式存儲在純文件中

db.password =隱窩:XXX

其中XXX是加密密碼。

請幫助我確定如何解密密碼。 不需要實際讀取普通文件的幫助。 請假定我已經在C#程序的變量中存儲了用戶名和加密密碼(沒有“ crypt:”部分)。

我一直在嘗試對類似問題的現有示例進行修改,但是它們專注於AES,到目前為止,我還沒有成功。

基本上,應在C#中構建以下函數:

private string DecryptPassword(string username, string encryptedPassword)

我該怎么做?

該軟件是開源的,可以在這里找到

一個測試案例: DecryptPassword("mark", "19215E9576DE6A96D5F03FE1D3073DCC")應該返回密碼getmeback 基本密碼短語為cypherkeymark 我已經在不同的機器上進行了測試,並且使用相同的用戶名,“哈希”密碼始終相同。

AltEncrypter用來從密碼派生密鑰的方法很糟糕。 這種方法應該被使用。

首先,它不安全。 除非計算量大,否則密鑰推導算法是不安全的。 而是使用scrypt,bcrypt或PBKDF2之類的算法。

其次,SHA1PRNG算法定義不明確。 說“它使用SHA-1”還不夠。 哈希多久執行一次? 它不是標准化的; 您將無法在另一個平台(例如.Net)上請求“ SHA1PRNG”,並獲得相同的輸出。

因此,請放棄這種加密方法,並使用由知識淵博的人編寫和維護的簡單而安全的方法。

不幸的是,問題並沒有就此結束。 AltEncrypter實用程序以最壞的方式使用(不是秘密的)密鑰可逆地加密身份驗證密碼。 這根本不安全。 它允許攻擊者解密用戶密碼,並將其用於其他系統上的用戶帳戶。

幾乎就像該系統的作者想要造成安全災難一樣。

這是一個說明。 我無法添加注釋,但我認為用於加密的算法不是SHA1。 它是“ DESEDE / ECB / PKCS5Padding”,查看創建(獲得)加密密碼的行

SHA1PRNG是偽隨機數生成器,用於生成在加密過程中使用的第一個隨機數,以便即使在加密相同的純文本時也可以生成“不同”的加密。

另一個重要的事情是用於加密的密鑰,我的意思是:

 KeyGenerator kGen = KeyGenerator.getInstance("DESEDE");
 kGen.init(168, sr);
 Key key = kGen.generateKey(); <-- this key

該密鑰用於加密和解密,但是我看不到它的存儲位置。 我的意思是每次都會重新生成。 應該從某個地方存儲和檢索它,而不要重新生成它,因為如果不使用相同的密鑰,就不可能解密任何密文。

這是使用一些解決方法的答案。

我嘗試過重新實現提供GNU實現(是opensource )的SHA1PRNG ,但是它給出的結果與適當的SUN實現的結果不同(因此它們是不同的,或者我以錯誤的方式實現了它)。 因此,我實現了一種變通方法:調用Java程序為我們派生密鑰。 是的,這非常便宜,但是暫時可以解決。 如果有人在我的SHA1PRNG實現中看到錯誤,請告訴我。

因此,首先,這是一個簡單的Java程序,它將使用SHA1PRNG生成器為種子提供168位密鑰。 只需在stdout上輸出它,空格即可。

import java.io.UnsupportedEncodingException;
import java.security.*;
import javax.crypto.*;

public class PasswordDeriver {

    public static void main(String[] args) throws NoSuchAlgorithmException, UnsupportedEncodingException {
        if(args.length == 0){
            System.out.println("You need to give the seed as the first argument.");
            return;
        }
        //Use Java to generate the key used for encryption and decryption.
        String passPhrase = args[args.length-1];

        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        sr.setSeed(passPhrase.getBytes("UTF8"));
        KeyGenerator kGen = KeyGenerator.getInstance("DESEDE");
        kGen.init(168, sr);
        Key key = kGen.generateKey();

        //Key is generated, now output it. 
        //System.out.println("Format: " + key.getFormat());
        byte[] k = key.getEncoded();
        for(int i=0; i < k.length; i++){
            System.out.print(String.format((i == k.length - 1) ? "%X" : "%X ", k[i]));
        }        
    }
}

將其保存為PasswordDeriver.java ,使用javac <file>進行javac <file> ,然后將得到的PasswordDeriver.class與此編譯程序放在同一文件夾中:(實際的C#程序)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System.IO;
using System.Diagnostics;

namespace OpenbravoDecrypter
{
    class Program
    {
        static void Main(string[] args)
        {
            var decrypted = Decrypt("19215E9576DE6A96D5F03FE1D3073DCC", "mark");
            Console.ReadLine();
        }

        static string Decrypt(string ciphertext, string username)
        {
            //Ciphertext is given as a hex string, convert it back to bytes
            if(ciphertext.Length % 2 == 1) ciphertext = "0" + ciphertext; //pad a zero left is necessary
            byte[] ciphertext_bytes = new byte[ciphertext.Length / 2];
            for(int i=0; i < ciphertext.Length; i+=2)
                ciphertext_bytes[i / 2] = Convert.ToByte(ciphertext.Substring(i, 2), 16);

            //Get an instance of a tripple-des descryption
            TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();        
            tdes.Mode = CipherMode.ECB; //ECB as Cipher Mode
            tdes.Padding = PaddingMode.PKCS7; //PKCS7 padding (same as PKCS5, good enough)

            byte[] key_bytes = DeriveKeyWorkAround(username);
            Console.WriteLine("Derived Key: " + BitConverter.ToString(key_bytes));
            //Start the decryption, give it the key, and null for the IV.
            var decryptor = tdes.CreateDecryptor(key_bytes, null);
            //Decrypt it.
            var plain = decryptor.TransformFinalBlock(ciphertext_bytes, 0, ciphertext_bytes.Length);

            //Output the result as hex string and as UTF8 encoded string
            Console.WriteLine("Plaintext Bytes: " + BitConverter.ToString(plain));
            var s = Encoding.UTF8.GetString(plain);
            Console.WriteLine("Plaintext UTF-8: " + s);
            return s;
        }

        /* Work around the fact that we don't have a C# implementation of SHA1PRNG by calling into a custom-prepared java file..*/
        static byte[] DeriveKeyWorkAround(string username)
        {
            username = "cypherkey" + username;
            string procOutput = "";
            //Invoke java on our file
            Process p = new Process();
            p.StartInfo.FileName = "cmd.exe";
            p.StartInfo.Arguments = "/c java PasswordDeriver \"" + username + "\"";
            p.StartInfo.RedirectStandardOutput = true;
            p.OutputDataReceived += (e, d) => procOutput += d.Data;
            p.StartInfo.UseShellExecute = false;
            p.Start();
            p.BeginOutputReadLine();
            p.WaitForExit();

            //Convert it back
            byte[] key = procOutput.Split(' ').Select(hex => Convert.ToByte(hex, 16)).ToArray();

            return key;
        }


        /* This function copies the functionality of the GNU Implementation of SHA1PRNG. 
         * Currently, it's broken, meaning that it doesn't produce the same output as the SUN implenetation of SHA1PRNG.
         * Case 1: the GNU implementation is the same as the SUN implementation, and this re-implementation is just wrong somewhere
         * Case 2: the GNU implementation is not the same the SUN implementation, therefore you'd need to reverse engineer some existing
         * SUN implementation and correct this method. 
       */
        static byte[] DeriveKey(string username)
        {
            //adjust
            username = "cypherkey" + username;
            byte[] user = Encoding.UTF8.GetBytes(username);
            //Do SHA1 magic
            var sha1 = new SHA1CryptoServiceProvider();
            var seed = new byte[20];
            byte[] data = new byte[40];
            int seedpos = 0;
            int datapos = 0;

            //init stuff
            byte[] digestdata;
            digestdata = sha1.ComputeHash(data);
            Array.Copy(digestdata, 0, data, 0, 20);

            /* seeding part */
            for (int i=0; i < user.Length; i++)
            {
                seed[seedpos++ % 20] ^= user[i];
            }
            seedpos %= 20;

            /* Generate output bytes */
            byte[] bytes = new byte[24]; //we need 24 bytes (= 192 bit / 8)

            int loc = 0;
            while (loc < bytes.Length)
            {
                int copy = Math.Min(bytes.Length - loc, 20 - datapos);

                if (copy > 0)
                {
                    Array.Copy(data, datapos, bytes, loc, copy);
                    datapos += copy;
                    loc += copy;
                }
                else
                {
                    // No data ready for copying, so refill our buffer.
                    Array.Copy(seed, 0, data, 20, 20);
                    byte[] digestdata2 = sha1.ComputeHash(data);
                    Array.Copy(digestdata2, 0, data, 0, 20);
                    datapos = 0;
                }
            }
            Console.WriteLine("GENERATED KEY:\n");
            for(int i=0; i < bytes.Length; i++)
            {
                Console.Write(bytes[i].ToString("X").PadLeft(2, '0'));
            }

            return bytes;
        } 
    }
}

您可以看到標准的東西,例如初始化三重DES加密提供程序,為其提供密鑰並在那里計算密文的解密。 它還包含SHA1PRNG的當前中斷的實現和解決方法。 鑒於java在當前環境變量的PATH中,此程序將產生輸出:

派生密鑰:86-EF-C1-F2-2F-97-D3-F1-34-49-23-89-E3-EC-29-80-02-92-52-40-49-5D-CD-C1

純文本字節:67-65-74-6D-65-62-61-63-6B

純文本UTF-8:getmeback

因此,這里具有解密功能(對其進行加密,只需將.CreateDecryptor()更改為.CreateEncryptor() )。 如果您忘記了執行密鑰派生的代碼,則解密代碼僅需要約20行代碼即可完成其工作。 因此,在回顧中,我的答案是其他想要將此解決方案設為100%C#的人的起點。 希望這可以幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM