获取文件数字签名证书信息

本文详细介绍了如何使用WinVerifyTrust和CryptQueryObject函数验证文件数字签名的有效性,并通过CryptMsgGetParam获取签名信息。文章进一步阐述了如何从签名信息中提取程序名、发布者链接和更多信息链接,以及如何获取时间戳证书信息和时间戳日期。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

验证文件数字签名是否有效可以使用函数 WinVerifyTrust
取得文件数字签名证书信息需要使用函数 CryptQueryObject。
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. // FileSign.cpp : 定义控制台应用程序的入口点。  
  2. //  
  3.   
  4. #include "stdafx.h"  
  5. #include <windows.h>  
  6. #include <wincrypt.h>  
  7. #include <wintrust.h>  
  8. #include <stdio.h>  
  9. #include <tchar.h>  
  10. #pragma comment(lib, "crypt32.lib")  
  11. #define ENCODING (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING)  
  12. typedef struct {  
  13.     LPWSTR lpszProgramName;//程序名  
  14.     LPWSTR lpszPublisherLink;//发布者链接  
  15.     LPWSTR lpszMoreInfoLink;//更多信息链接  
  16. } SPROG_PUBLISHERINFO, *PSPROG_PUBLISHERINFO;  
  17.   
  18. BOOL GetProgAndPublisherInfo(PCMSG_SIGNER_INFO pSignerInfo,  
  19.                              PSPROG_PUBLISHERINFO Info);  
  20. //获取时间戳日期  
  21. BOOL GetDateOfTimeStamp(PCMSG_SIGNER_INFO pSignerInfo, SYSTEMTIME *st);  
  22. //打印证书信息  
  23. BOOL PrintCertificateInfo(PCCERT_CONTEXT pCertContext);  
  24. //获取签名信息的时间戳  
  25. BOOL GetTimeStampSignerInfo(PCMSG_SIGNER_INFO pSignerInfo,  
  26.                             PCMSG_SIGNER_INFO *pCounterSignerInfo);  
  27. int _tmain(int argc, TCHAR *argv[])  
  28. {  
  29.     WCHAR szFileName[MAX_PATH];  
  30.     HCERTSTORE hStore = NULL;  
  31.     HCRYPTMSG hMsg = NULL;  
  32.     PCCERT_CONTEXT pCertContext = NULL;  
  33.     BOOL fResult;  
  34.     DWORD dwEncoding, dwContentType, dwFormatType;  
  35.     PCMSG_SIGNER_INFO pSignerInfo = NULL;  
  36.     PCMSG_SIGNER_INFO pCounterSignerInfo = NULL;  
  37.     DWORD dwSignerInfo;  
  38.     CERT_INFO CertInfo;  
  39.     SPROG_PUBLISHERINFO ProgPubInfo;  
  40.     SYSTEMTIME st;  
  41.     ZeroMemory(&ProgPubInfo, sizeof(ProgPubInfo));  
  42.     __try  
  43.     {  
  44.         if (argc != 2)  
  45.         {  
  46.             _tprintf(_T("Usage: SignedFileInfo <filename>\n"));  
  47.             return 0;  
  48.         }  
  49. #ifdef UNICODE  
  50.         lstrcpynW(szFileName, argv[1], MAX_PATH);  
  51. #else  
  52.         if (mbstowcs(szFileName, argv[1], MAX_PATH) == -1)  
  53.         {  
  54.             printf("Unable to convert to unicode.\n");  
  55.             __leave;  
  56.         }  
  57. #endif  
  58.         // Get message handle and store handle from the signed file.  
  59.         //查询签名信息  
  60.         fResult = CryptQueryObject(CERT_QUERY_OBJECT_FILE,  
  61.             szFileName,  
  62.             CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,  
  63.             CERT_QUERY_FORMAT_FLAG_BINARY,  
  64.             0,  
  65.             &dwEncoding,  
  66.             &dwContentType,  
  67.             &dwFormatType,  
  68.             &hStore,  
  69.             &hMsg,  
  70.             NULL);  
  71.         if (!fResult)  
  72.         {  
  73.             _tprintf(_T("CryptQueryObject failed with %x\n"), GetLastError());  
  74.             __leave;  
  75.         }  
  76.         // Get signer information size.  
  77.         fResult = CryptMsgGetParam(hMsg,  
  78.             CMSG_SIGNER_INFO_PARAM,  
  79.             0,  
  80.             NULL,  
  81.             &dwSignerInfo);  
  82.         if (!fResult)  
  83.         {  
  84.             _tprintf(_T("CryptMsgGetParam failed with %x\n"), GetLastError());  
  85.             __leave;  
  86.         }  
  87.         // Allocate memory for signer information.  
  88.         pSignerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, dwSignerInfo);  
  89.         if (!pSignerInfo)  
  90.         {  
  91.             _tprintf(_T("Unable to allocate memory for Signer Info.\n"));  
  92.             __leave;  
  93.         }  
  94.         // Get Signer Information.  
  95.         fResult = CryptMsgGetParam(hMsg,  
  96.             CMSG_SIGNER_INFO_PARAM,  
  97.             0,  
  98.             (PVOID)pSignerInfo,  
  99.             &dwSignerInfo);  
  100.         if (!fResult)  
  101.         {  
  102.             _tprintf(_T("CryptMsgGetParam failed with %x\n"), GetLastError());  
  103.             __leave;  
  104.         }  
  105.         // Get program name and publisher information from  
  106.         // signer info structure.  
  107.         //获取程序名和发布者信息  
  108.         if (GetProgAndPublisherInfo(pSignerInfo, &ProgPubInfo))  
  109.         {  
  110.             if (ProgPubInfo.lpszProgramName != NULL)  
  111.             {  
  112.                 wprintf(L"Program Name : %s\n",  
  113.                     ProgPubInfo.lpszProgramName);  
  114.             }  
  115.             if (ProgPubInfo.lpszPublisherLink != NULL)  
  116.             {  
  117.                 wprintf(L"Publisher Link : %s\n",  
  118.                     ProgPubInfo.lpszPublisherLink);  
  119.             }  
  120.             if (ProgPubInfo.lpszMoreInfoLink != NULL)  
  121.             {  
  122.                 wprintf(L"MoreInfo Link : %s\n",  
  123.                     ProgPubInfo.lpszMoreInfoLink);  
  124.             }  
  125.         }  
  126.         _tprintf(_T("\n"));  
  127.         // Search for the signer certificate in the temporary  
  128.         // certificate store.  
  129.         CertInfo.Issuer = pSignerInfo->Issuer;  
  130.         CertInfo.SerialNumber = pSignerInfo->SerialNumber;  
  131.         pCertContext = CertFindCertificateInStore(hStore,  
  132.             ENCODING,  
  133.             0,  
  134.             CERT_FIND_SUBJECT_CERT,  
  135.             (PVOID)&CertInfo,  
  136.             NULL);  
  137.         if (!pCertContext)  
  138.         {  
  139.             _tprintf(_T("CertFindCertificateInStore failed with %x\n"),  
  140.                 GetLastError());  
  141.             __leave;  
  142.         }  
  143.         // Print Signer certificate information.  
  144.         _tprintf(_T("Signer Certificate:\n\n"));  
  145.         PrintCertificateInfo(pCertContext);  
  146.         _tprintf(_T("\n"));  
  147.         // Get the timestamp certificate signerinfo structure.  
  148.         if (GetTimeStampSignerInfo(pSignerInfo, &pCounterSignerInfo))  
  149.         {  
  150.             // Search for Timestamp certificate in the temporary  
  151.             // certificate store.  
  152.             CertInfo.Issuer = pCounterSignerInfo->Issuer;  
  153.             CertInfo.SerialNumber = pCounterSignerInfo->SerialNumber;  
  154.             pCertContext = CertFindCertificateInStore(hStore,  
  155.                 ENCODING,  
  156.                 0,  
  157.                 CERT_FIND_SUBJECT_CERT,  
  158.                 (PVOID)&CertInfo,  
  159.                 NULL);  
  160.             if (!pCertContext)  
  161.             {  
  162.                 _tprintf(_T("CertFindCertificateInStore failed with %x\n"),  
  163.                     GetLastError());  
  164.                 __leave;  
  165.             }  
  166.             // Print timestamp certificate information.  
  167.             _tprintf(_T("TimeStamp Certificate:\n\n"));  
  168.             PrintCertificateInfo(pCertContext);  
  169.             _tprintf(_T("\n"));  
  170.             // Find Date of timestamp.  
  171.             if (GetDateOfTimeStamp(pCounterSignerInfo, &st))  
  172.             {  
  173.                 _tprintf(_T("Date of TimeStamp : %02d/%02d/%04d %02d:%02d\n"),  
  174.                     st.wMonth,  
  175.                     st.wDay,  
  176.                     st.wYear,  
  177.                     st.wHour,  
  178.                     st.wMinute);  
  179.             }  
  180.             _tprintf(_T("\n"));  
  181.         }  
  182.     }  
  183.     __finally  
  184.     {  
  185.         // Clean up.  
  186.         if (ProgPubInfo.lpszProgramName != NULL)  
  187.             LocalFree(ProgPubInfo.lpszProgramName);  
  188.         if (ProgPubInfo.lpszPublisherLink != NULL)  
  189.             LocalFree(ProgPubInfo.lpszPublisherLink);  
  190.         if (ProgPubInfo.lpszMoreInfoLink != NULL)  
  191.             LocalFree(ProgPubInfo.lpszMoreInfoLink);  
  192.         if (pSignerInfo != NULL) LocalFree(pSignerInfo);  
  193.         if (pCounterSignerInfo != NULL) LocalFree(pCounterSignerInfo);  
  194.         if (pCertContext != NULL) CertFreeCertificateContext(pCertContext);  
  195.         if (hStore != NULL) CertCloseStore(hStore, 0);  
  196.         if (hMsg != NULL) CryptMsgClose(hMsg);  
  197.     }  
  198.     return 0;  
  199. }  
  200. BOOL PrintCertificateInfo(PCCERT_CONTEXT pCertContext)  
  201. {  
  202.     BOOL fReturn = FALSE;  
  203.     LPTSTR szName = NULL;  
  204.     DWORD dwData;  
  205.     __try  
  206.     {  
  207.         // Print Serial Number.  
  208.         _tprintf(_T("Serial Number: "));  
  209.         dwData = pCertContext->pCertInfo->SerialNumber.cbData;  
  210.         for (DWORD n = 0; n < dwData; n++)  
  211.         {  
  212.             _tprintf(_T("%02x "),  
  213.                 pCertContext->pCertInfo->SerialNumber.pbData[dwData - (n + 1)]);  
  214.         }  
  215.         _tprintf(_T("\n"));  
  216.         // Get Issuer name size.  
  217.         if (!(dwData = CertGetNameString(pCertContext,  
  218.             CERT_NAME_SIMPLE_DISPLAY_TYPE,  
  219.             CERT_NAME_ISSUER_FLAG,  
  220.             NULL,  
  221.             NULL,  
  222.             0)))  
  223.         {  
  224.             _tprintf(_T("CertGetNameString failed.\n"));  
  225.             __leave;  
  226.         }  
  227.         // Allocate memory for Issuer name.  
  228.         szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(TCHAR));  
  229.         if (!szName)  
  230.         {  
  231.             _tprintf(_T("Unable to allocate memory for issuer name.\n"));  
  232.             __leave;  
  233.         }  
  234.         // Get Issuer name.  
  235.         if (!(CertGetNameString(pCertContext,  
  236.             CERT_NAME_SIMPLE_DISPLAY_TYPE,  
  237.             CERT_NAME_ISSUER_FLAG,  
  238.             NULL,  
  239.             szName,  
  240.             dwData)))  
  241.         {  
  242.             _tprintf(_T("CertGetNameString failed.\n"));  
  243.             __leave;  
  244.         }  
  245.         // print Issuer name.  
  246.         _tprintf(_T("Issuer Name: %s\n"), szName);  
  247.         LocalFree(szName);  
  248.         szName = NULL;  
  249.         // Get Subject name size.  
  250.         if (!(dwData = CertGetNameString(pCertContext,  
  251.             CERT_NAME_SIMPLE_DISPLAY_TYPE,  
  252.             0,  
  253.             NULL,  
  254.             NULL,  
  255.             0)))  
  256.         {  
  257.             _tprintf(_T("CertGetNameString failed.\n"));  
  258.             __leave;  
  259.         }  
  260.         // Allocate memory for subject name.  
  261.         szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(TCHAR));  
  262.         if (!szName)  
  263.         {  
  264.             _tprintf(_T("Unable to allocate memory for subject name.\n"));  
  265.             __leave;  
  266.         }  
  267.         // Get subject name.  
  268.         if (!(CertGetNameString(pCertContext,  
  269.             CERT_NAME_SIMPLE_DISPLAY_TYPE,  
  270.             0,  
  271.             NULL,  
  272.             szName,  
  273.             dwData)))  
  274.         {  
  275.             _tprintf(_T("CertGetNameString failed.\n"));  
  276.             __leave;  
  277.         }  
  278.         // Print Subject Name.  
  279.         _tprintf(_T("Subject Name: %s\n"), szName);  
  280.         fReturn = TRUE;  
  281.     }  
  282.     __finally  
  283.     {  
  284.         if (szName != NULL) LocalFree(szName);  
  285.     }  
  286.     return fReturn;  
  287. }  
  288. LPWSTR AllocateAndCopyWideString(LPCWSTR inputString)  
  289. {  
  290.     LPWSTR outputString = NULL;  
  291.     outputString = (LPWSTR)LocalAlloc(LPTR,  
  292.         (wcslen(inputString) + 1) * sizeof(WCHAR));  
  293.     if (outputString != NULL)  
  294.     {  
  295.         lstrcpyW(outputString, inputString);  
  296.     }  
  297.     return outputString;  
  298. }  
  299. BOOL GetProgAndPublisherInfo(PCMSG_SIGNER_INFO pSignerInfo,  
  300.                              PSPROG_PUBLISHERINFO Info)  
  301. {  
  302.     BOOL fReturn = FALSE;  
  303.     PSPC_SP_OPUS_INFO OpusInfo = NULL;  
  304.     DWORD dwData;  
  305.     BOOL fResult;  
  306.     __try  
  307.     {  
  308.         // Loop through authenticated attributes and find  
  309.         // SPC_SP_OPUS_INFO_OBJID OID.  
  310.         for (DWORD n = 0; n < pSignerInfo->AuthAttrs.cAttr; n++)  
  311.         {  
  312.             if (lstrcmpA(SPC_SP_OPUS_INFO_OBJID,  
  313.                 pSignerInfo->AuthAttrs.rgAttr[n].pszObjId) == 0)  
  314.             {  
  315.                 // Get Size of SPC_SP_OPUS_INFO structure.  
  316.                 fResult = CryptDecodeObject(ENCODING,  
  317.                     SPC_SP_OPUS_INFO_OBJID,  
  318.                     pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].pbData,  
  319.                     pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].cbData,  
  320.                     0,  
  321.                     NULL,  
  322.                     &dwData);  
  323.                 if (!fResult)  
  324.                 {  
  325.                     _tprintf(_T("CryptDecodeObject failed with %x\n"),  
  326.                         GetLastError());  
  327.                     __leave;  
  328.                 }  
  329.                 // Allocate memory for SPC_SP_OPUS_INFO structure.  
  330.                 OpusInfo = (PSPC_SP_OPUS_INFO)LocalAlloc(LPTR, dwData);  
  331.                 if (!OpusInfo)  
  332.                 {  
  333.                     _tprintf(_T("Unable to allocate memory for Publisher Info.\n"));  
  334.                     __leave;  
  335.                 }  
  336.                 // Decode and get SPC_SP_OPUS_INFO structure.  
  337.                 fResult = CryptDecodeObject(ENCODING,  
  338.                     SPC_SP_OPUS_INFO_OBJID,  
  339.                     pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].pbData,  
  340.                     pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].cbData,  
  341.                     0,  
  342.                     OpusInfo,  
  343.                     &dwData);  
  344.                 if (!fResult)  
  345.                 {  
  346.                     _tprintf(_T("CryptDecodeObject failed with %x\n"),  
  347.                         GetLastError());  
  348.                     __leave;  
  349.                 }  
  350.                 // Fill in Program Name if present.  
  351.                 if (OpusInfo->pwszProgramName)  
  352.                 {  
  353.                     Info->lpszProgramName =  
  354.                         AllocateAndCopyWideString(OpusInfo->pwszProgramName);  
  355.                 }  
  356.                 else  
  357.                     Info->lpszProgramName = NULL;  
  358.                 // Fill in Publisher Information if present.  
  359.                 if (OpusInfo->pPublisherInfo)  
  360.                 {  
  361.                     switch (OpusInfo->pPublisherInfo->dwLinkChoice)  
  362.                     {  
  363.                     case SPC_URL_LINK_CHOICE:  
  364.                         Info->lpszPublisherLink =  
  365.                             AllocateAndCopyWideString(OpusInfo->pPublisherInfo->pwszUrl);  
  366.                         break;  
  367.                     case SPC_FILE_LINK_CHOICE:  
  368.                         Info->lpszPublisherLink =  
  369.                             AllocateAndCopyWideString(OpusInfo->pPublisherInfo->pwszFile);  
  370.                         break;  
  371.                     default:  
  372.                         Info->lpszPublisherLink = NULL;  
  373.                         break;  
  374.                     }  
  375.                 }  
  376.                 else  
  377.                 {  
  378.                     Info->lpszPublisherLink = NULL;  
  379.                 }  
  380.                 // Fill in More Info if present.  
  381.                 if (OpusInfo->pMoreInfo)  
  382.                 {  
  383.                     switch (OpusInfo->pMoreInfo->dwLinkChoice)  
  384.                     {  
  385.                     case SPC_URL_LINK_CHOICE:  
  386.                         Info->lpszMoreInfoLink =  
  387.                             AllocateAndCopyWideString(OpusInfo->pMoreInfo->pwszUrl);  
  388.                         break;  
  389.                     case SPC_FILE_LINK_CHOICE:  
  390.                         Info->lpszMoreInfoLink =  
  391.                             AllocateAndCopyWideString(OpusInfo->pMoreInfo->pwszFile);  
  392.                         break;  
  393.                     default:  
  394.                         Info->lpszMoreInfoLink = NULL;  
  395.                         break;  
  396.                     }  
  397.                 }  
  398.                 else  
  399.                 {  
  400.                     Info->lpszMoreInfoLink = NULL;  
  401.                 }  
  402.                 fReturn = TRUE;  
  403.                 break// Break from for loop.  
  404.             } // lstrcmp SPC_SP_OPUS_INFO_OBJID  
  405.         } // for  
  406.     }  
  407.     __finally  
  408.     {  
  409.         if (OpusInfo != NULL) LocalFree(OpusInfo);  
  410.     }  
  411.     return fReturn;  
  412. }  
  413. BOOL GetDateOfTimeStamp(PCMSG_SIGNER_INFO pSignerInfo, SYSTEMTIME *st)  
  414. {  
  415.     BOOL fResult;  
  416.     FILETIME lft, ft;  
  417.     DWORD dwData;  
  418.     BOOL fReturn = FALSE;  
  419.     // Loop through authenticated attributes and find  
  420.     // szOID_RSA_signingTime OID.  
  421.     for (DWORD n = 0; n < pSignerInfo->AuthAttrs.cAttr; n++)  
  422.     {  
  423.         if (lstrcmpA(szOID_RSA_signingTime,  
  424.             pSignerInfo->AuthAttrs.rgAttr[n].pszObjId) == 0)  
  425.         {  
  426.             // Decode and get FILETIME structure.  
  427.             dwData = sizeof(ft);  
  428.             fResult = CryptDecodeObject(ENCODING,  
  429.                 szOID_RSA_signingTime,  
  430.                 pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].pbData,  
  431.                 pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].cbData,  
  432.                 0,  
  433.                 (PVOID)&ft,  
  434.                 &dwData);  
  435.             if (!fResult)  
  436.             {  
  437.                 _tprintf(_T("CryptDecodeObject failed with %x\n"),  
  438.                     GetLastError());  
  439.                 break;  
  440.             }  
  441.             // Convert to local time.  
  442.             FileTimeToLocalFileTime(&ft, &lft);  
  443.             FileTimeToSystemTime(&lft, st);  
  444.             fReturn = TRUE;  
  445.             break// Break from for loop.  
  446.         } //lstrcmp szOID_RSA_signingTime  
  447.     } // for  
  448.     return fReturn;  
  449. }  
  450. BOOL GetTimeStampSignerInfo(PCMSG_SIGNER_INFO pSignerInfo, PCMSG_SIGNER_INFO *pCounterSignerInfo)  
  451. {  
  452.     PCCERT_CONTEXT pCertContext = NULL;  
  453.     BOOL fReturn = FALSE;  
  454.     BOOL fResult;  
  455.     DWORD dwSize;  
  456.     __try  
  457.     {  
  458.         *pCounterSignerInfo = NULL;  
  459.         // Loop through unathenticated attributes for  
  460.         // szOID_RSA_counterSign OID.  
  461.         for (DWORD n = 0; n < pSignerInfo->UnauthAttrs.cAttr; n++)  
  462.         {  
  463.             if (lstrcmpA(pSignerInfo->UnauthAttrs.rgAttr[n].pszObjId,  
  464.                 szOID_RSA_counterSign) == 0)  
  465.             {  
  466.                 // Get size of CMSG_SIGNER_INFO structure.  
  467.                 fResult = CryptDecodeObject(ENCODING,  
  468.                     PKCS7_SIGNER_INFO,  
  469.                     pSignerInfo->UnauthAttrs.rgAttr[n].rgValue[0].pbData,  
  470.                     pSignerInfo->UnauthAttrs.rgAttr[n].rgValue[0].cbData,  
  471.                     0,  
  472.                     NULL,  
  473.                     &dwSize);  
  474.                 if (!fResult)  
  475.                 {  
  476.                     _tprintf(_T("CryptDecodeObject failed with %x\n"),  
  477.                         GetLastError());  
  478.                     __leave;  
  479.                 }  
  480.                 // Allocate memory for CMSG_SIGNER_INFO.  
  481.                 *pCounterSignerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, dwSize);  
  482.                 if (!*pCounterSignerInfo)  
  483.                 {  
  484.                     _tprintf(_T("Unable to allocate memory for timestamp info.\n"));  
  485.                     __leave;  
  486.                 }  
  487.                 // Decode and get CMSG_SIGNER_INFO structure  
  488.                 // for timestamp certificate.  
  489.                 fResult = CryptDecodeObject(ENCODING,  
  490.                     PKCS7_SIGNER_INFO,  
  491.                     pSignerInfo->UnauthAttrs.rgAttr[n].rgValue[0].pbData,  
  492.                     pSignerInfo->UnauthAttrs.rgAttr[n].rgValue[0].cbData,  
  493.                     0,  
  494.                     (PVOID)*pCounterSignerInfo,  
  495.                     &dwSize);  
  496.                 if (!fResult)  
  497.                 {  
  498.                     _tprintf(_T("CryptDecodeObject failed with %x\n"),  
  499.                         GetLastError());  
  500.                     __leave;  
  501.                 }  
  502.                 fReturn = TRUE;  
  503.                 break// Break from for loop.  
  504.             }  
  505.         }  
  506.     }  
  507.     __finally  
  508.     {  
  509.         // Clean up.  
  510.         if (pCertContext != NULL) CertFreeCertificateContext(pCertContext);  
  511.     }  
  512.     return fReturn;  
  513. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值