cer, pfx 创建,并且读取公钥/密钥,加解密 (C#程序实现)

PKI技术(public key infrastructure)里面,cer文件和pfx文件是很常见的。通常cer文件里面保存着公钥以及用户的一些信息,pfx里面则含有私钥和公钥。

用makecert.exe可以创建公钥证书和私钥证书,具体看

http://msdn.microsoft.com/zh-cn/library/bfsktky3(v=vs.110).aspx

http://blog.csdn.net/hacode/article/details/4240238


这里使用程序的方法来创建。参考了http://www.cnblogs.com/luminji/archive/2010/10/28/1863179.html

下面的代码封装了一个类,可以在store里面创建一个认证,并且导出到cer,pfx,然后从store,cer,pfx读取信息

[csharp]  view plain copy
  1. public sealed class DataCertificate     
  2.     {    
  3.         #region 生成证书     
  4.         /// <summary>     
  5.         /// 根据指定的证书名和makecert全路径生成证书(包含公钥和私钥,并保存在MY存储区)     
  6.         /// </summary>     
  7.         /// <param name="subjectName"></param>     
  8.         /// <param name="makecertPath"></param>     
  9.         /// <returns></returns>     
  10.         public static bool CreateCertWithPrivateKey(string subjectName, string makecertPath)     
  11.         {     
  12.             subjectName = "CN=" + subjectName;     
  13.             string param = " -pe -ss my -n \"" + subjectName + "\" ";     
  14.             try    
  15.             {     
  16.                 Process p = Process.Start(makecertPath, param);     
  17.                 p.WaitForExit();     
  18.                 p.Close();     
  19.             }     
  20.             catch (Exception e)     
  21.             {     
  22.                 return false;     
  23.             }     
  24.             return true;     
  25.         }    
  26.         #endregion    
  27.   
  28.         #region 文件导入导出     
  29.         /// <summary>     
  30.         /// 从WINDOWS证书存储区的个人MY区找到主题为subjectName的证书,     
  31.         /// 并导出为pfx文件,同时为其指定一个密码     
  32.         /// 并将证书从个人区删除(如果isDelFromstor为true)     
  33.         /// </summary>     
  34.         /// <param name="subjectName">证书主题,不包含CN=</param>     
  35.         /// <param name="pfxFileName">pfx文件名</param>     
  36.         /// <param name="password">pfx文件密码</param>     
  37.         /// <param name="isDelFromStore">是否从存储区删除</param>     
  38.         /// <returns></returns>     
  39.         public static bool ExportToPfxFile(string subjectName, string pfxFileName,     
  40.             string password, bool isDelFromStore)     
  41.         {     
  42.             subjectName = "CN=" + subjectName;     
  43.             X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);     
  44.             store.Open(OpenFlags.ReadWrite);     
  45.             X509Certificate2Collection storecollection = (X509Certificate2Collection)store.Certificates;     
  46.             foreach (X509Certificate2 x509 in storecollection)     
  47.             {     
  48.                 if (x509.Subject == subjectName)     
  49.                 {     
  50.                     Debug.Print(string.Format("certificate name: {0}", x509.Subject));     
  51.     
  52.                     byte[] pfxByte = x509.Export(X509ContentType.Pfx, password);     
  53.                     using (FileStream fileStream = new FileStream(pfxFileName, FileMode.Create))     
  54.                     {     
  55.                         // Write the data to the file, byte by byte.     
  56.                         for (int i = 0; i < pfxByte.Length; i++)     
  57.                             fileStream.WriteByte(pfxByte[i]);     
  58.                         // Set the stream position to the beginning of the file.     
  59.                         fileStream.Seek(0, SeekOrigin.Begin);     
  60.                         // Read and verify the data.     
  61.                         for (int i = 0; i < fileStream.Length; i++)     
  62.                         {     
  63.                             if (pfxByte[i] != fileStream.ReadByte())     
  64.                             {     
  65.                                 fileStream.Close();     
  66.                                 return false;     
  67.                             }     
  68.                         }     
  69.                         fileStream.Close();     
  70.                     }     
  71.                     if( isDelFromStore == true)     
  72.                         store.Remove(x509);     
  73.                 }     
  74.             }     
  75.             store.Close();     
  76.             store = null;     
  77.             storecollection = null;     
  78.             return true;     
  79.         }     
  80.         /// <summary>     
  81.         /// 从WINDOWS证书存储区的个人MY区找到主题为subjectName的证书,     
  82.         /// 并导出为CER文件(即,只含公钥的)     
  83.         /// </summary>     
  84.         /// <param name="subjectName"></param>     
  85.         /// <param name="cerFileName"></param>     
  86.         /// <returns></returns>     
  87.         public static bool ExportToCerFile(string subjectName, string cerFileName)     
  88.         {     
  89.             subjectName = "CN=" + subjectName;     
  90.             X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);     
  91.             store.Open(OpenFlags.ReadWrite);     
  92.             X509Certificate2Collection storecollection = (X509Certificate2Collection)store.Certificates;     
  93.             foreach (X509Certificate2 x509 in storecollection)     
  94.             {     
  95.                 if (x509.Subject == subjectName)     
  96.                 {     
  97.                     Debug.Print(string.Format("certificate name: {0}", x509.Subject));     
  98.                     //byte[] pfxByte = x509.Export(X509ContentType.Pfx, password);     
  99.                     byte[] cerByte = x509.Export(X509ContentType.Cert);     
  100.                     using (FileStream fileStream = new FileStream(cerFileName, FileMode.Create))     
  101.                     {     
  102.                         // Write the data to the file, byte by byte.     
  103.                         for (int i = 0; i < cerByte.Length; i++)     
  104.                             fileStream.WriteByte(cerByte[i]);     
  105.                         // Set the stream position to the beginning of the file.     
  106.                         fileStream.Seek(0, SeekOrigin.Begin);     
  107.                         // Read and verify the data.     
  108.                         for (int i = 0; i < fileStream.Length; i++)     
  109.                         {     
  110.                             if (cerByte[i] != fileStream.ReadByte())     
  111.                             {     
  112.                                 fileStream.Close();     
  113.                                 return false;     
  114.                             }     
  115.                         }     
  116.                         fileStream.Close();     
  117.                     }     
  118.                 }     
  119.             }     
  120.             store.Close();     
  121.             store = null;     
  122.             storecollection = null;     
  123.             return true;     
  124.         }    
  125.         #endregion    
  126.   
  127.         #region 从证书中获取信息     
  128.         /// <summary>     
  129.         /// 根据私钥证书得到证书实体,得到实体后可以根据其公钥和私钥进行加解密     
  130.         /// 加解密函数使用DEncrypt的RSACryption类     
  131.         /// </summary>     
  132.         /// <param name="pfxFileName"></param>     
  133.         /// <param name="password"></param>     
  134.         /// <returns></returns>     
  135.         public static X509Certificate2 GetCertificateFromPfxFile(string pfxFileName,     
  136.             string password)     
  137.         {     
  138.             try    
  139.             {     
  140.                 return new X509Certificate2(pfxFileName, password, X509KeyStorageFlags.Exportable);     
  141.             }     
  142.             catch (Exception e)     
  143.             {     
  144.                 return null;     
  145.             }     
  146.         }     
  147.         /// <summary>     
  148.         /// 到存储区获取证书     
  149.         /// </summary>     
  150.         /// <param name="subjectName"></param>     
  151.         /// <returns></returns>     
  152.         public static X509Certificate2 GetCertificateFromStore(string subjectName)     
  153.         {     
  154.             subjectName = "CN=" + subjectName;     
  155.             X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);     
  156.             store.Open(OpenFlags.ReadWrite);     
  157.             X509Certificate2Collection storecollection = (X509Certificate2Collection)store.Certificates;     
  158.             foreach (X509Certificate2 x509 in storecollection)     
  159.             {     
  160.                 if (x509.Subject == subjectName)     
  161.                 {     
  162.                     return x509;     
  163.                 }     
  164.             }     
  165.             store.Close();     
  166.             store = null;     
  167.             storecollection = null;     
  168.             return null;     
  169.         }     
  170.        /// <summary>     
  171.         /// 根据公钥证书,返回证书实体     
  172.         /// </summary>     
  173.         /// <param name="cerPath"></param>     
  174.         public static X509Certificate2 GetCertFromCerFile(string cerPath)     
  175.         {     
  176.             try    
  177.             {     
  178.                 return new X509Certificate2(cerPath);     
  179.             }     
  180.             catch (Exception e)     
  181.             {      
  182.                 return null;     
  183.             }                 
  184.         }    
  185.         #endregion            
  186.     }    

两个RSA加解密辅助函数:

[csharp]  view plain copy
  1. static string RSADecrypt(string xmlPrivateKey, string m_strDecryptString)     
  2. {     
  3.     RSACryptoServiceProvider provider = new RSACryptoServiceProvider();     
  4.     provider.FromXmlString(xmlPrivateKey);     
  5.     byte[] rgb = Convert.FromBase64String(m_strDecryptString);     
  6.     byte[] bytes = provider.Decrypt(rgb, false);     
  7.     return new UnicodeEncoding().GetString(bytes);     
  8. }     
  9. /// <summary>     
  10. /// RSA加密     
  11. /// </summary>     
  12. /// <param name="xmlPublicKey"></param>     
  13. /// <param name="m_strEncryptString"></param>     
  14. /// <returns></returns>     
  15. static string RSAEncrypt(string xmlPublicKey, string m_strEncryptString)     
  16. {     
  17.     RSACryptoServiceProvider provider = new RSACryptoServiceProvider();     
  18.     provider.FromXmlString(xmlPublicKey);     
  19.     byte[] bytes = new UnicodeEncoding().GetBytes(m_strEncryptString);     
  20.     return Convert.ToBase64String(provider.Encrypt(bytes, false));     
  21. }    


使用例子,下面的代码做了几个事情

1. 在个人store里面创建了一个认证, 从认证里面读取信息得到一个X509Certificate2的对象,这个对象内部包含公钥和私钥,然后做了次rsa加解密测试。

2. 从store里面导出一个cer文件,因为cer文件并没有私钥,只有公钥。测试代码就是用公钥加密然后用前面得到的私钥解密。

3. 导出一个pfx文件,pfx包括公钥和私钥,可以自己加解密。

这是个很简单的例子,但是对于理解cer文件和pfx文件已经公钥私钥应该有帮助。

[csharp]  view plain copy
  1. // 在personal(个人)里面创建一个foo的证书  
  2. DataCertificate.CreateCertWithPrivateKey("foo""C:\\Program Files (x86)\\Windows Kits\\8.1\\bin\\x64\\makecert.exe");  
  3.   
  4. // 获取证书  
  5. X509Certificate2 c1 = DataCertificate.GetCertificateFromStore("foo");  
  6.   
  7. string keyPublic = c1.PublicKey.Key.ToXmlString(false);  // 公钥  
  8. string keyPrivate = c1.PrivateKey.ToXmlString(true);  // 私钥  
  9.   
  10. string cypher = RSAEncrypt(keyPublic, "程序员");  // 加密  
  11. string plain = RSADecrypt(keyPrivate, cypher);  // 解密  
  12.   
  13. Debug.Assert(plain == "程序员");  
  14.   
  15. // 生成一个cert文件  
  16. DataCertificate.ExportToCerFile("foo""d:\\mycert\\foo.cer");  
  17.   
  18. X509Certificate2 c2 = DataCertificate.GetCertFromCerFile("d:\\mycert\\foo.cer");  
  19.   
  20. string keyPublic2 = c2.PublicKey.Key.ToXmlString(false);  
  21.   
  22. bool b = keyPublic2 == keyPublic;  
  23. string cypher2 = RSAEncrypt(keyPublic2, "程序员2");  // 加密  
  24. string plain2 = RSADecrypt(keyPrivate, cypher2);  // 解密, cer里面并没有私钥,所以这里使用前面得到的私钥来解密  
  25.   
  26. Debug.Assert(plain2 == "程序员2");  
  27.   
  28. // 生成一个pfx, 并且从store里面删除  
  29. DataCertificate.ExportToPfxFile("foo""d:\\mycert\\foo.pfx""111"true);  
  30.   
  31. X509Certificate2 c3 = DataCertificate.GetCertificateFromPfxFile("d:\\mycert\\foo.pfx""111");  
  32.   
  33. string keyPublic3 = c3.PublicKey.Key.ToXmlString(false);  // 公钥  
  34. string keyPrivate3 = c3.PrivateKey.ToXmlString(true);  // 私钥  
  35.   
  36. string cypher3 = RSAEncrypt(keyPublic3, "程序员3");  // 加密  
  37. string plain3 = RSADecrypt(keyPrivate3, cypher3);  // 解密  
  38.   
  39. Debug.Assert(plain3 == "程序员3");  


附:完整代码

[csharp]  view plain copy
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Diagnostics;  
  4. using System.IO;  
  5. using System.Linq;  
  6. using System.Security.Cryptography;  
  7. using System.Security.Cryptography.X509Certificates;  
  8. using System.Text;  
  9.   
  10.   
  11. namespace ConsoleApplication1  
  12. {  
  13.     public sealed class DataCertificate     
  14.     {    
  15.         #region 生成证书     
  16.         /// <summary>     
  17.         /// 根据指定的证书名和makecert全路径生成证书(包含公钥和私钥,并保存在MY存储区)     
  18.         /// </summary>     
  19.         /// <param name="subjectName"></param>     
  20.         /// <param name="makecertPath"></param>     
  21.         /// <returns></returns>     
  22.         public static bool CreateCertWithPrivateKey(string subjectName, string makecertPath)     
  23.         {     
  24.             subjectName = "CN=" + subjectName;     
  25.             string param = " -pe -ss my -n \"" + subjectName + "\" ";     
  26.             try    
  27.             {     
  28.                 Process p = Process.Start(makecertPath, param);     
  29.                 p.WaitForExit();     
  30.                 p.Close();     
  31.             }     
  32.             catch (Exception e)     
  33.             {     
  34.                 return false;     
  35.             }     
  36.             return true;     
  37.         }    
  38.         #endregion    
  39.   
  40.         #region 文件导入导出     
  41.         /// <summary>     
  42.         /// 从WINDOWS证书存储区的个人MY区找到主题为subjectName的证书,     
  43.         /// 并导出为pfx文件,同时为其指定一个密码     
  44.         /// 并将证书从个人区删除(如果isDelFromstor为true)     
  45.         /// </summary>     
  46.         /// <param name="subjectName">证书主题,不包含CN=</param>     
  47.         /// <param name="pfxFileName">pfx文件名</param>     
  48.         /// <param name="password">pfx文件密码</param>     
  49.         /// <param name="isDelFromStore">是否从存储区删除</param>     
  50.         /// <returns></returns>     
  51.         public static bool ExportToPfxFile(string subjectName, string pfxFileName,     
  52.             string password, bool isDelFromStore)     
  53.         {     
  54.             subjectName = "CN=" + subjectName;     
  55.             X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);     
  56.             store.Open(OpenFlags.ReadWrite);     
  57.             X509Certificate2Collection storecollection = (X509Certificate2Collection)store.Certificates;     
  58.             foreach (X509Certificate2 x509 in storecollection)     
  59.             {     
  60.                 if (x509.Subject == subjectName)     
  61.                 {     
  62.                     Debug.Print(string.Format("certificate name: {0}", x509.Subject));     
  63.     
  64.                     byte[] pfxByte = x509.Export(X509ContentType.Pfx, password);     
  65.                     using (FileStream fileStream = new FileStream(pfxFileName, FileMode.Create))     
  66.                     {     
  67.                         // Write the data to the file, byte by byte.     
  68.                         for (int i = 0; i < pfxByte.Length; i++)     
  69.                             fileStream.WriteByte(pfxByte[i]);     
  70.                         // Set the stream position to the beginning of the file.     
  71.                         fileStream.Seek(0, SeekOrigin.Begin);     
  72.                         // Read and verify the data.     
  73.                         for (int i = 0; i < fileStream.Length; i++)     
  74.                         {     
  75.                             if (pfxByte[i] != fileStream.ReadByte())     
  76.                             {     
  77.                                 fileStream.Close();     
  78.                                 return false;     
  79.                             }     
  80.                         }     
  81.                         fileStream.Close();     
  82.                     }     
  83.                     if( isDelFromStore == true)     
  84.                         store.Remove(x509);     
  85.                 }     
  86.             }     
  87.             store.Close();     
  88.             store = null;     
  89.             storecollection = null;     
  90.             return true;     
  91.         }     
  92.         /// <summary>     
  93.         /// 从WINDOWS证书存储区的个人MY区找到主题为subjectName的证书,     
  94.         /// 并导出为CER文件(即,只含公钥的)     
  95.         /// </summary>     
  96.         /// <param name="subjectName"></param>     
  97.         /// <param name="cerFileName"></param>     
  98.         /// <returns></returns>     
  99.         public static bool ExportToCerFile(string subjectName, string cerFileName)     
  100.         {     
  101.             subjectName = "CN=" + subjectName;     
  102.             X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);     
  103.             store.Open(OpenFlags.ReadWrite);     
  104.             X509Certificate2Collection storecollection = (X509Certificate2Collection)store.Certificates;     
  105.             foreach (X509Certificate2 x509 in storecollection)     
  106.             {     
  107.                 if (x509.Subject == subjectName)     
  108.                 {     
  109.                     Debug.Print(string.Format("certificate name: {0}", x509.Subject));     
  110.                     //byte[] pfxByte = x509.Export(X509ContentType.Pfx, password);     
  111.                     byte[] cerByte = x509.Export(X509ContentType.Cert);     
  112.                     using (FileStream fileStream = new FileStream(cerFileName, FileMode.Create))     
  113.                     {     
  114.                         // Write the data to the file, byte by byte.     
  115.                         for (int i = 0; i < cerByte.Length; i++)     
  116.                             fileStream.WriteByte(cerByte[i]);     
  117.                         // Set the stream position to the beginning of the file.     
  118.                         fileStream.Seek(0, SeekOrigin.Begin);     
  119.                         // Read and verify the data.     
  120.                         for (int i = 0; i < fileStream.Length; i++)     
  121.                         {     
  122.                             if (cerByte[i] != fileStream.ReadByte())     
  123.                             {     
  124.                                 fileStream.Close();     
  125.                                 return false;     
  126.                             }     
  127.                         }     
  128.                         fileStream.Close();     
  129.                     }     
  130.                 }     
  131.             }     
  132.             store.Close();     
  133.             store = null;     
  134.             storecollection = null;     
  135.             return true;     
  136.         }    
  137.         #endregion    
  138.   
  139.         #region 从证书中获取信息     
  140.         /// <summary>     
  141.         /// 根据私钥证书得到证书实体,得到实体后可以根据其公钥和私钥进行加解密     
  142.         /// 加解密函数使用DEncrypt的RSACryption类     
  143.         /// </summary>     
  144.         /// <param name="pfxFileName"></param>     
  145.         /// <param name="password"></param>     
  146.         /// <returns></returns>     
  147.         public static X509Certificate2 GetCertificateFromPfxFile(string pfxFileName,     
  148.             string password)     
  149.         {     
  150.             try    
  151.             {     
  152.                 return new X509Certificate2(pfxFileName, password, X509KeyStorageFlags.Exportable);     
  153.             }     
  154.             catch (Exception e)     
  155.             {     
  156.                 return null;     
  157.             }     
  158.         }     
  159.         /// <summary>     
  160.         /// 到存储区获取证书     
  161.         /// </summary>     
  162.         /// <param name="subjectName"></param>     
  163.         /// <returns></returns>     
  164.         public static X509Certificate2 GetCertificateFromStore(string subjectName)     
  165.         {     
  166.             subjectName = "CN=" + subjectName;     
  167.             X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);     
  168.             store.Open(OpenFlags.ReadWrite);     
  169.             X509Certificate2Collection storecollection = (X509Certificate2Collection)store.Certificates;     
  170.             foreach (X509Certificate2 x509 in storecollection)     
  171.             {     
  172.                 if (x509.Subject == subjectName)     
  173.                 {     
  174.                     return x509;     
  175.                 }     
  176.             }     
  177.             store.Close();     
  178.             store = null;     
  179.             storecollection = null;     
  180.             return null;     
  181.         }     
  182.        /// <summary>     
  183.         /// 根据公钥证书,返回证书实体     
  184.         /// </summary>     
  185.         /// <param name="cerPath"></param>     
  186.         public static X509Certificate2 GetCertFromCerFile(string cerPath)     
  187.         {     
  188.             try    
  189.             {     
  190.                 return new X509Certificate2(cerPath);     
  191.             }     
  192.             catch (Exception e)     
  193.             {      
  194.                 return null;     
  195.             }                 
  196.         }    
  197.         #endregion            
  198.     }    
  199.   
  200.     class Program  
  201.     {  
  202.         static string RSADecrypt(string xmlPrivateKey, string m_strDecryptString)     
  203.         {     
  204.             RSACryptoServiceProvider provider = new RSACryptoServiceProvider();     
  205.             provider.FromXmlString(xmlPrivateKey);     
  206.             byte[] rgb = Convert.FromBase64String(m_strDecryptString);     
  207.             byte[] bytes = provider.Decrypt(rgb, false);     
  208.             return new UnicodeEncoding().GetString(bytes);     
  209.         }     
  210.         /// <summary>     
  211.         /// RSA加密     
  212.         /// </summary>     
  213.         /// <param name="xmlPublicKey"></param>     
  214.         /// <param name="m_strEncryptString"></param>     
  215.         /// <returns></returns>     
  216.         static string RSAEncrypt(string xmlPublicKey, string m_strEncryptString)     
  217.         {     
  218.             RSACryptoServiceProvider provider = new RSACryptoServiceProvider();     
  219.             provider.FromXmlString(xmlPublicKey);     
  220.             byte[] bytes = new UnicodeEncoding().GetBytes(m_strEncryptString);     
  221.             return Convert.ToBase64String(provider.Encrypt(bytes, false));     
  222.         }    
  223.   
  224.         static void Main(string[] args)  
  225.         {  
  226.             // 在personal(个人)里面创建一个foo的证书  
  227.             DataCertificate.CreateCertWithPrivateKey("foo""C:\\Program Files (x86)\\Windows Kits\\8.1\\bin\\x64\\makecert.exe");  
  228.   
  229.             // 获取证书  
  230.             X509Certificate2 c1 = DataCertificate.GetCertificateFromStore("foo");  
  231.   
  232.             string keyPublic = c1.PublicKey.Key.ToXmlString(false);  // 公钥  
  233.             string keyPrivate = c1.PrivateKey.ToXmlString(true);  // 私钥  
  234.   
  235.             string cypher = RSAEncrypt(keyPublic, "程序员");  // 加密  
  236.             string plain = RSADecrypt(keyPrivate, cypher);  // 解密  
  237.   
  238.             Debug.Assert(plain == "程序员");  
  239.   
  240.             // 生成一个cert文件  
  241.             DataCertificate.ExportToCerFile("foo""d:\\mycert\\foo.cer");  
  242.   
  243.             X509Certificate2 c2 = DataCertificate.GetCertFromCerFile("d:\\mycert\\foo.cer");  
  244.   
  245.             string keyPublic2 = c2.PublicKey.Key.ToXmlString(false);  
  246.   
  247.             bool b = keyPublic2 == keyPublic;  
  248.             string cypher2 = RSAEncrypt(keyPublic2, "程序员2");  // 加密  
  249.             string plain2 = RSADecrypt(keyPrivate, cypher2);  // 解密, cer里面并没有私钥,所以这里使用前面得到的私钥来解密  
  250.   
  251.             Debug.Assert(plain2 == "程序员2");  
  252.   
  253.             // 生成一个pfx, 并且从store里面删除  
  254.             DataCertificate.ExportToPfxFile("foo""d:\\mycert\\foo.pfx""111"true);  
  255.   
  256.             X509Certificate2 c3 = DataCertificate.GetCertificateFromPfxFile("d:\\mycert\\foo.pfx""111");  
  257.   
  258.             string keyPublic3 = c3.PublicKey.Key.ToXmlString(false);  // 公钥  
  259.             string keyPrivate3 = c3.PrivateKey.ToXmlString(true);  // 私钥  
  260.   
  261.             string cypher3 = RSAEncrypt(keyPublic3, "程序员3");  // 加密  
  262.             string plain3 = RSADecrypt(keyPrivate3, cypher3);  // 解密  
  263.   
  264.             Debug.Assert(plain3 == "程序员3");  
  265.         }  
  266.     }  
  267. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值