DSA算法的跨平台调用

6 篇文章 0 订阅
2 篇文章 0 订阅

背景

DSA(Digital signature algorithm)是一个非对称加密的签名算法。使用私钥对明文的摘要进行加密,使用配对的公钥对密文进行验证。算法的介绍,甚至是程序demo在网上可以找到很多,但绝大多数都是在同一个平台中进行签名和验证(如C++、.NET或java),当进行跨平台的验证时却发现问题并不简单。

开始时使用openssl在C++下验证java发过来的一个签名,尝试过各种方案最终以失败告终。转而使用crypto++(http://www.cryptopp.com/),我使用的版本是5.6.2。

在Codeproject上找到一篇Jeffrey Walton的文章(Cryptographic Interoperability: Digital Signatures),里面介绍了java签名和c++签名格式其实是不一样的,java用DER格式,而C++用IEEE的P1363格式,在验证时需要转换相应的格式。作者提供了在.Net、C++、Java三个平台上的签名及验证的Demo。

关键代码逻辑贴出来大家共同学习一下,一下三段代码分别实现了公私密钥的生成、签名、验证签名。完整的项目包可在原文中下载,或者点击下面每个章节的链接:

DSA签名之C++(Crypto++)

// CryptoPP.cpp
//
#include "stdafx.h"

#include <dsa.h>
using CryptoPP::DSA;
using CryptoPP::DSA_DER;
using CryptoPP::DSA_P1363;

#include <pubkey.h>
using CryptoPP::PrivateKey;
using CryptoPP::PublicKey;

#include <osrng.h>
using CryptoPP::AutoSeededRandomPool;

#include <files.h>
using CryptoPP::FileSource;
using CryptoPP::FileSink;
using CryptoPP::StringSource;
using CryptoPP::StringSink;

bool CreateDSAKeys();
bool SignDSAMessage();
bool VerifyDSAMessage();

int main(int argc, char* argv[])
{
    CreateDSAKeys();

    SignDSAMessage();

    VerifyDSAMessage();

    return 0;
}

bool CreateDSAKeys()
{
    AutoSeededRandomPool prng;

    try
    {
        // Crypto++ Key Generation
        DSA::Signer signer;        
        PrivateKey& privateKey = signer.AccessPrivateKey();
        privateKey.GenerateRandom( prng );

        DSA::Verifier verifier( signer );
        PublicKey& publicKey = verifier.AccessPublicKey();

        // Crypto++ Save Keys
        privateKey.Save(FileSink("private.dsa.cpp.key"));
        publicKey.Save(FileSink("public.dsa.cpp.key"));
    }

    catch( CryptoPP::Exception& ex )
    {
        cerr << "Caught Error:" << endl;
        cerr << " " << ex.what() << endl;

        return false;
    }

    catch( std::exception& ex )
    {
        cerr << "Caught Error:" << endl;
        cerr << " " << ex.what() << endl;

        return false;
    }

    return true;
}


bool VerifyDSAMessage()
{
    try
    {
        //
        // Crypto++ Load the Pubic Key
        //
        DSA::Verifier verifier;
        PublicKey& publicKey = verifier.AccessPublicKey();

        // publicKey.Load(FileSource("public.dsa.cpp.key", true));
        // publicKey.Load(FileSource("public.dsa.java.key", true));
        publicKey.Load(FileSource("public.dsa.cs.key", true));

        //
        // Retrieve the Message and the Signature on Message
        //
        string message, signature;

        // C++
        FileSource( "dsa.cpp.msg", true, new StringSink( message ) );
        FileSource( "dsa.cpp.sig", true, new StringSink( signature ) );

        // Java
        // FileSource( "dsa.java.msg", true, new StringSink( message ) );
        // FileSource( "dsa.java.sig", true, new StringSink( signature ) );

        // C#
        // FileSource( "dsa.cs.msg", true, new StringSink( message ) );
        // FileSource( "dsa.cs.sig", true, new StringSink( signature ) );

        //*
        // --- Begin Java Specific Conversion ---

        //
        // Convert: DER -> P1363
        //

        // Java is DER Encoded Sequence.
        // Crypto++ is P1363 format.
        /*
        {            
            // From dsa.h: If toFormat == DSA_P1363,
            //   bufferSize must equal publicKey.SignatureLength()
            size_t length = verifier.SignatureLength();

            // A buffer for the conversion
            byte* buffer = new byte[ length ];

            // Shit or Go Blind. You make the choice.
            if( !buffer ) { return false; }

            // Reuse length
            length = CryptoPP::DSAConvertSignatureFormat( 
                buffer, length, DSA_P1363, (const byte*)signature.c_str(),
                signature.length(), DSA_DER );
            
            // Reinitialize signature so that it can be used
            //   in the verifier below with minimal effort
            signature = string( (const char*)buffer, length );
            delete[] buffer;
        }
        */
        // --- End Java Specific Conversion ---
        //*/

        //
        // Verify the Signature on the Message
        //
        bool result = verifier.VerifyMessage(
            (const byte*)message.c_str(), message.length(),
            (const byte*)signature.c_str(), signature.length() );

        if( result )
        {
            cout << "Signature on Message verified" << endl;
        }
        else
        {
            cerr << "Signature on Message not verified" << endl;
            
            return false;
        }

        //
        // Convert the Message
        //
        int nChars = MultiByteToWideChar( CP_UTF8, 0,
            message.c_str(), -1, NULL, NULL );

        wchar_t* p = new wchar_t[ nChars ];
        if( !p ) { return false; }

        MultiByteToWideChar( CP_UTF8, 0, message.c_str(), -1, p, nChars );
        wstring wide( p );

        delete[] p;

    }

    catch( CryptoPP::Exception& ex )
    {
        cerr << "Caught Error:" << endl;
        cerr << " " << ex.what() << endl;

        return false;
    }

    catch( std::exception& ex )
    {
        cerr << "Caught Error:" << endl;
        cerr << " " << ex.what() << endl;

        return false;
    }

    return true;
}

bool SignDSAMessage()
{
    try
    {
        //
        // Crypto++ Load Private Key
        //
        DSA::Signer signer;        
        PrivateKey& privateKey = signer.AccessPrivateKey();        
        privateKey.Load(FileSource("private.dsa.cpp.key", true));
        
        // Convert the Message
        wstring wide = L"Crypto Interop: \u9aa8";
        int nChars = WideCharToMultiByte( CP_UTF8, 0,
            wide.c_str(), -1, NULL, 0, NULL, FALSE );

        char* p = new char[ nChars ];
        if( !p ) { return false; }

        nChars = WideCharToMultiByte( CP_UTF8, 0,
            wide.c_str(), -1, p, nChars, NULL, FALSE );

        string narrow( p );
        delete[] p;

        //
        // Sign the Message
        //

        // Set up for SignMessage()
        byte* s = new byte[ signer.MaxSignatureLength() ];
        if( !s ) { return false; }

        // Sign...
        AutoSeededRandomPool prng;
        size_t length = signer.SignMessage( prng,
            (const byte*) narrow.c_str(), narrow.length(), s );

        // Convenience
        string signature( (const char*)s, length );

        // --- Begin Java Specific Conversion ---

        //
        // Convert: P1363 -> DER
        //

        // Crypto++ uses P1363 format.
        // Java requires a DER encoded sequence.
        /*
        {
            // Determine size of required buffer
            // We can call with a null buffer and 0 size
            // when going in this direction
            length = CryptoPP::DSAConvertSignatureFormat( 
                NULL, 0, DSA_DER, (const byte*)signature.c_str(),
                signature.length(), DSA_P1363 );

            // A buffer for the conversion
            byte* buffer = new byte[ length ];

            // Shit or Go Blind. You make the choice.
            if( !buffer ) { return false; }

            // Reuse length
            length = CryptoPP::DSAConvertSignatureFormat( 
                buffer, length, DSA_DER, (const byte*)signature.c_str(),
                signature.length(), DSA_P1363 );
            
            // Reinitialize signature so that it can be used
            //   in the verifier below with minimal effort
            signature = string( (const char*)buffer, length );
            delete[] buffer;
        }
        */
        // --- End Java Specific Conversion ---

        //
        // Save the Message and the Signature on Message
        //

        // mofs: message filestream
        // sofs: signature filestream
        ofstream mofs, sofs;
        mofs.open("dsa.cpp.msg", ios_base::binary | ios_base::trunc );
        sofs.open("dsa.cpp.sig", ios_base::binary | ios_base::trunc );

        // Save Original Message
        mofs.write( narrow.c_str(), (int)narrow.length() );

        // Save Signature on Message
        sofs.write( (const char*)signature.c_str(), (int)length );

        // Cleanup
        sofs.close();
        mofs.close();
    }

    catch( CryptoPP::Exception& ex )
    {
        cerr << "Caught Error:" << endl;
        cerr << " " << ex.what() << endl;

        return false;
    }

    catch( std::exception& ex )
    {
        cerr << "Caught Error:" << endl;
        cerr << " " << ex.what() << endl;

        return false;
    }

    return true;
}

在Linux环境上编译crypto++的时候比较顺利,而在AIX一个比较老的平台上编译的时候各种报错:

"pubkey.h", line 1606.39: 1540-0716 (S) The template argument "CryptoPP::TF_ES" does not match the template parameter "class ALG_INFO".

升级了xlC,并将GNUmakefile修改后终于编译成功:

CXXFLAGS = -DNDEBUG -q64 -qkeepinlines -brtl -bhalt:5
# -O3 fails to link on Cygwin GCC version 4.5.3
# -fPIC is supported. Please report any breakage of -fPIC as a bug.
# CXXFLAGS += -fPIC
# the following options reduce code size, but breaks link or makes link very slow on some systems
# CXXFLAGS += -ffunction-sections -fdata-sections
LDFLAGS =  -q64  -bnoentry -qmkshrobj  -lpthread  
CXX = xlC

# 省略。。。

libcryptopp.so: $(LIBOBJS)
	$(CXX) -o $@ $(LIBOBJS) $(LDFLAGS)  

此外,makefile中用了wildcard作为cpp源代码列表。曾不小心把测试程序也放在同一个目录下,导致编出来的libcryptopp.so中包含一个main入口,再次编测试程序的时候就报冲突了。这里也吐槽一下crypto++的目录层级(只有一级)比openssl的目录层级差太多了。

在编译测试程序时上面的C++代码加载key文件的地方仍报错,无法将FileSource转换为CryptoPP::BufferedTransformation。从Crypto++的代码来看FileSource是BufferedTransformation的子类,因此改成下面的写法:

FileSource *fs = new FileSource(_priKeyPath, true);
privateKey.Load((CryptoPP::BufferedTransformation&)*fs);
delete fs;
而在vs2013升级后的项目直接编译运行时总是报错,无奈注释掉了cryptlib.h中1091行的一个校验,不影响功能。

voidDoQuickSanityCheck()const        {/*ThrowIfInvalid(NullRNG(),0);*/}

此外还遇到了内存泄露问题,最终发现是MTd/MT/MDd/MD选项的设置问题,这个可以搜到很多介绍的文章。

DSA签名之Java

package JavaInteropSign;

import java.io.*;
import java.security.*;
import java.security.spec.*;
import javax.swing.JOptionPane;

/**
 * @author jeffrey walton
 **/
public class Main {

  static String ALGORITHM = "DSA";
  
  public static void main(String[] args) {
    try {

      CreateDSAKeys();

      SignDSAMessage();
      
      VerifyDSAMessage();

    } catch (Exception e) {
      System.err.println("Main: Exception " + e.toString());
    }
  }

  private static void VerifyDSAMessage() {

    try {
      
      // Load the public
      PublicKey publicKey = LoadPublicKey("public.dsa.java.key");

      // Load the message from file
      FileInputStream mis = new FileInputStream("dsa.java.msg");
      byte[] message = new byte[mis.available()];
      mis.read(message); mis.close();

      // Display the resurrected string
      JOptionPane.showMessageDialog(null,
          new String(message, 0, message.length, "UTF-8"));       

      // Load the signature of the message from file
      FileInputStream sis = new FileInputStream("dsa.java.sig");
      byte[] signature = new byte[sis.available()];
      sis.read(signature); sis.close();

      // Initialize Signature Object
      Signature verifier = Signature.getInstance(ALGORITHM);
      verifier.initVerify(publicKey);

      // Load the message into the Verifier Object
      verifier.update(message);

      // Verify the Signature on the Message
      boolean result = verifier.verify(signature);
      
      StringBuilder sb = new StringBuilder();
      if( result )
      {
        sb.append("Message Verified:\n");
        sb.append(new String(message, 0, message.length, "UTF-8"));
      }
      else
      {
        sb.append("Message Not Verified");
      }
      
      JOptionPane.showMessageDialog(null, sb.toString());

    } catch (Exception e) {
      System.err.println("VerifyDSAMessage: " + e.toString());
    }
  }
  
  private static void SignDSAMessage() {

    try {
      // Retrieve the Private Key
      PrivateKey privateKey = LoadPrivateKey("private.dsa.java.key");

      // Create the signer object
      Signature signer = Signature.getInstance(ALGORITHM);
      signer.initSign(privateKey, new SecureRandom());

      // Prepare the Message
      String s = "Crypto Interop: \u9aa8";

      // Save the binary of the String which we will sign
      byte[] message = s.getBytes("UTF-8");

      // Insert the message into the signer object
      signer.update(message);
      byte[] signature = signer.sign();

      // mos: message filestream
      // sos: signature filestream
      FileOutputStream mos = new FileOutputStream("dsa.java.msg");
      mos.write(message);

      FileOutputStream sos = new FileOutputStream("dsa.java.sig");
      sos.write(signature);

    } catch (Exception e) {
      System.err.println("SignDSAMessage: " + e.toString());
    }
  }

  private static PublicKey LoadPublicKey(String filename) {

    PublicKey key = null;

    try {

      FileInputStream fis = new FileInputStream(filename);
      byte[] b = new byte[fis.available()];
      fis.read(b);
      fis.close();

      X509EncodedKeySpec spec = new X509EncodedKeySpec(b);

      KeyFactory factory = KeyFactory.getInstance(ALGORITHM);
      key = factory.generatePublic(spec);

    } catch (Exception e) {
      System.err.println("LoadPublicKey: " + e.toString());
    }

    return key;
  }
  
  private static PrivateKey LoadPrivateKey( String filename) {

    PrivateKey key = null;

    try {

      FileInputStream fis = new FileInputStream(filename);
      byte[] b = new byte[fis.available()];
      fis.read(b);
      fis.close();

      PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(b);

      KeyFactory factory = KeyFactory.getInstance("DSA");
      key = factory.generatePrivate(spec);

    } catch (Exception e) {
      System.err.println("LoadPrivateKey: " + e.toString());
    }

    return key;
  }

  private static void CreateDSAKeys() throws NoSuchAlgorithmException {

    // http://java.sun.com/j2se/1.4.2/docs/guide/security/CryptoSpec.html
    // KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA", "SUN");
    KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGORITHM);

    kpg.initialize(1024, new SecureRandom());
    KeyPair keys = kpg.generateKeyPair();

    PrivateKey privateKey = keys.getPrivate();
    PublicKey publicKey = keys.getPublic();

    // Serialize Keys
    SaveKey("private.dsa.java.key", privateKey);
    SaveKey("public.dsa.java.key", publicKey);
  }

  static void SaveKey(String filename, Key key) {
    try {

      if (null == key) {
        throw new Exception("key is null.");
      }

      FileOutputStream fos = new FileOutputStream(filename);

      // PKCS #8 for Private, X.509 for Public
      // File will contain OID 1.2.840.10040.4.1 (DSA)
      // http://java.sun.com/j2se/1.4.2/docs/api/java/security/Key.html
      fos.write(key.getEncoded());

      fos.close();

    } catch (Exception e) {
      System.err.println("SaveEncodedKey: Exception " + e.toString());
    }
  }
}

其实java的实现逻辑比较通用,在网上能搜到大量的dsa签名和验证的文章都和这段代码类似:PKCS8私钥加密,X509公钥验证。

DSA签名之C#

using System;
using System.IO;
using System.Text;
using System.Windows.Forms;
using System.Security.Cryptography;

namespace CSInteropSign
{
  class Program
  {
    internal static void Main(string[] args)
    {
      CreateDsaKeys();

      SignDsaMessage();

      VerifyDsaMessage();
    }

    private static void VerifyDsaMessage()
    {
      //
      // Load the Public Key
      //   X.509 Format
      //
      AsnKeyParser keyParser =
        new AsnKeyParser("public.dsa.cs.key");

      DSAParameters publicKey = keyParser.ParseDSAPublicKey();

      //
      // Initailize the CSP
      //
      CspParameters csp = new CspParameters();

      // Cannot use PROV_DSS_DH
      const int PROV_DSS = 3;
      csp.ProviderType = PROV_DSS;

      const int AT_SIGNATURE = 2;
      csp.KeyNumber = AT_SIGNATURE;

      csp.KeyContainerName = "DSA Test (OK to Delete)";

      //
      // Initialize the Provider
      //
      DSACryptoServiceProvider dsa =
        new DSACryptoServiceProvider(csp);
      dsa.PersistKeyInCsp = false;

      //
      // The moment of truth...
      //
      dsa.ImportParameters(publicKey);

      //
      // Load the message
      //   Message is m
      //
      byte[] message = null;
      using (BinaryReader reader = new BinaryReader(
          new FileStream("dsa.cs.msg", FileMode.Open, FileAccess.Read)))
      {
        FileInfo info = new FileInfo("dsa.cs.msg");
        message = reader.ReadBytes((int)info.Length);
      }

      //
      // Load the signature
      //   Signature is (r,s)
      //
      byte[] signature = null;
      using (BinaryReader reader = new BinaryReader(
          new FileStream("dsa.cs.sig", FileMode.Open, FileAccess.Read)))
      {
        FileInfo info = new FileInfo("dsa.cs.sig");
        signature = reader.ReadBytes((int)info.Length);
      }

      //
      // Compute h(m)
      //
      SHA1 sha = new SHA1CryptoServiceProvider();
      byte[] hash = sha.ComputeHash(message);

      //
      // Initialize Verifier
      //
      DSASignatureDeformatter verifier =
        new DSASignatureDeformatter(dsa);
      verifier.SetHashAlgorithm("SHA1");

      if (verifier.VerifySignature(hash, signature))
      {
        UTF8Encoding utf8 = new UTF8Encoding();
        String s = utf8.GetString(message);

        MessageBox.Show("Message Verified. Recovered String:\n" + s);
      }
      else
      {
        MessageBox.Show("Message Not Verified.");
      }

      // See http://blogs.msdn.com/tess/archive/2007/10/31/
      //   asp-net-crash-system-security-cryptography-cryptographicexception.aspx
      dsa.Clear();
    }

    private static void SignDsaMessage()
    {
      //
      // Load the Private Key
      //   PKCS#8 Format
      //
      AsnKeyParser keyParser =
        new AsnKeyParser("private.dsa.cs.key");

      DSAParameters privateKey = keyParser.ParseDSAPrivateKey();

      //
      // Initailize the CSP
      //   Supresses creation of a new key
      //
      CspParameters csp = new CspParameters();
      csp.KeyContainerName = "DSA Test (OK to Delete)";

      // Cannot use PROV_DSS_DH
      const int PROV_DSS = 3;
      csp.ProviderType = PROV_DSS;

      const int AT_SIGNATURE = 2;
      csp.KeyNumber = AT_SIGNATURE;

      //
      // Initialize the Provider
      //
      DSACryptoServiceProvider dsa =
        new DSACryptoServiceProvider(csp);
      dsa.PersistKeyInCsp = false;

      //
      // The moment of truth...
      //
      dsa.ImportParameters(privateKey);

      //
      // Sign the Message
      //
      DSASignatureFormatter signer =
        new DSASignatureFormatter(dsa);
      signer.SetHashAlgorithm("SHA1");

      // The one and only
      String m = "Crypto Interop: \u9aa8";
      byte[] message = Encoding.GetEncoding("UTF-8").GetBytes(m);

      // h(m)
      SHA1 sha = new SHA1CryptoServiceProvider();
      byte[] hash = sha.ComputeHash(message);

      // Create the Signature for h(m)
      byte[] signature = signer.CreateSignature(hash);

      // Write the message
      using (BinaryWriter writer = new BinaryWriter(
          new FileStream("dsa.cs.msg", FileMode.Create,
              FileAccess.ReadWrite)))
      {
        writer.Write(message);
      }

      // Write the signature on the message
      using (BinaryWriter writer = new BinaryWriter(
          new FileStream("dsa.cs.sig", FileMode.Create,
              FileAccess.ReadWrite)))
      {
        writer.Write(signature);
      }

      // See http://blogs.msdn.com/tess/archive/2007/10/31/
      //   asp-net-crash-system-security-cryptography-cryptographicexception.aspx
      dsa.Clear();
    }

    private static void CreateDsaKeys()
    {
      CspParameters csp = new CspParameters();

      csp.KeyContainerName = "DSA Test (OK to Delete)";

      const int PROV_DSS_DH = 13;
      csp.ProviderType = PROV_DSS_DH;

      const int AT_SIGNATURE = 2;
      csp.KeyNumber = AT_SIGNATURE;

      DSACryptoServiceProvider dsa =
          new DSACryptoServiceProvider(1024, csp);
      dsa.PersistKeyInCsp = false;

      // Encoded key
      AsnKeyBuilder.AsnMessage key = null;

      // Private Key
      DSAParameters privateKey = dsa.ExportParameters(true);
      key = AsnKeyBuilder.PrivateKeyToPKCS8(privateKey);

      using (BinaryWriter writer = new BinaryWriter(
          new FileStream("private.dsa.cs.key", FileMode.Create,
              FileAccess.ReadWrite)))
      {
        writer.Write(key.GetBytes());
      }

      // Public Key
      DSAParameters publicKey = dsa.ExportParameters(false);
      key = AsnKeyBuilder.PublicKeyToX509(publicKey);

      using (BinaryWriter writer = new BinaryWriter(
          new FileStream("public.dsa.cs.key", FileMode.Create,
              FileAccess.ReadWrite)))
      {
        writer.Write(key.GetBytes());
      }

      // See http://blogs.msdn.com/tess/archive/2007/10/31/
      //   asp-net-crash-system-security-cryptography-cryptographicexception.aspx
      dsa.Clear();
    }
  }
}
C#使用的key格式是一个xml,在项目包中有对应的类处理key,这里就不赘述了。值得注意的是c#不能使用java生成的密钥,而C++中是可以的。

总结

密钥的生成可以考虑用java生成,如果需要将key写到配置文件中,建议将二进制key改为16进制编码。
签名的格式需要两个(或多个)平台协定统一,例如为了在网络上传输方便使用16进制的字符串:
302c02142a297556f6f6408c8557c070b25d5d7b7247aa8f02140e97dfe7cfc69a393db372b88f8e70fdeeabf067
(在Jeffrey Walton的文章中也介绍了签名的格式,然而这对解决在openssl中验证java签名并没有什么帮助)
16进制字符串与二进制的字节流的转换在c++中的实现也顺便写一下,就不另开贴了:
///
// 16进制string转为byte
///
string Hex2Byte(const string& s)
{
	int sigLen = s.length();
	unsigned char *bSig = new unsigned char[sigLen / 2 ];
	int index = 0;
	for (int i = 0; i < sigLen; i += 2, index++)
	{
		unsigned short byte = 0;
		std::istringstream iss(s.substr(i, 2).c_str());
		iss >> std::hex >> byte;
		bSig[index] = byte;
	}

	return string((const char*)bSig, sigLen/2);
}

///
// 转为16进制string
///
string Byte2Hex(const string& s)
{
	std::ostringstream ret;

	for (string::size_type i = 0; i < s.length(); ++i)
	{
		ret << std::hex << std::setfill('0') << std::setw(2) << ((int)s[i] & 0xff);
	}

	return ret.str();
}



  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值