ABAP RSA方式调用银行API


一、RSA简介

RSA是非对称加密的一种。

对称加密算法在加密和解密时使用的是同一个秘钥;

非对称加密算法需要一对密钥来加密解密,这两个密钥是公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥)。一个加密,一个解密,由双方各保留一个进行操作。这样,在不传输秘钥的情况下,双方可以针对信息进行加解密,避免了直接传输秘钥可能导致的安全问题。

二、RSA加解密与签名验签

一般来说,有两种场景:加密解密和签名验签

加密解密:使用公钥加密,私钥解密

签名验签:使用私钥加密(签名),公钥解密(验签)

例如:B生成一对密钥,向A提供公钥,B保留私钥

加密:防止信息泄露

当A系统传输数据给B系统,担心报文明文传输会导致信息泄露,则可使用公钥对报文进行加密,以防传输过程中信息泄露。B接收加密报文,通过私钥进行解密

签名:防止信息篡改

当B接收后返回消息给A,担心返回消息被修改,A接收到错误消息,则可使用秘钥进行签名,并将签名和返回报文一起发送给A。A通过公钥进行验签,发现签名解析后和返回报文数据一致,证明报文没有被中间拦截篡改

三、代码实现

1、证书加密解密与签名验签

使用秘钥生成PSE证书方式进行加密解密与签名验签

生成一对密钥后,将私钥文件RSA.key上传到SAP服务器,前后需要加标注,否则不能正常使用

通过命令生成PSE文件,此操作需要BASIS协助

/tmp/rsa > openssl req -x509 -sha256 -key rsa.key -out user1.cer -days 3650 -subj '/CN=user1'
/tmp/rsa > openssl pkcs12 -export -inkey rsa.key -in user1.cer -out user1.pfx -nodes
Enter Export Password:
Verifying - Enter Export Password:
/tmp/rsa > setenv SECUDIR $PWD
/tmp/rsa > sapgenpse import_p12 -x "" -p user1.pse user1.pfx
Found key 'INDEX=0,SIG=YES,ENC=YES,MD5-FINGERPRINT=3B9E 77DD E5E4 3371 19FA 2FCF D1CA 512F,KEYID=3D1E2BC7E33500A3D2A098B63FE9962A6B63318C'

!!! WARNING: For security reasons it is recommended to use a PIN/passphrase
!!! WARNING: which is at least 8 characters long and contains characters in
!!! WARNING: upper and lower case, numbers and non-alphanumeric symbols.

PSE "/tmp/rsa/user1.pse" was written

证书加密解密代码:

DATA: lv_output         TYPE xstring,
      lv_input_x        TYPE xstring,
      lv_input          TYPE string,
      lt_recipient_list TYPE STANDARD TABLE OF ssfinfo,
      ls_recipient_list LIKE LINE OF lt_recipient_list,
      lv_crc            TYPE ssfparms-ssfcrc.

DATA: lf_output        TYPE xstring.
DATA: lf_plain_input_x TYPE xstring.
DATA: lf_plain_input   TYPE string.
DATA: msg_file_name    TYPE string.
DATA: it_recipient_list TYPE STANDARD TABLE OF ssfinfo,
      wa_recipient_list LIKE LINE OF it_recipient_list.

"原文
lf_plain_input = '一段需要处理的报文'.

"转为xstring
CALLFUNCTION'SCMS_STRING_TO_XSTRING'
  EXPORTING
    text =lf_plain_input
  IMPORTING
    buffer =lf_plain_input_x.

"证书域名
wa_recipient_list-id ='CN=user1'.
APPENDwa_recipient_listTOit_recipient_list.

"加密函数
CALLFUNCTION'SSFW_KRN_ENVELOPE'
  EXPORTING
    str_format ='PKCS1-V1.5'
    str_pab ='/tmp/rsa/user1.pse'"服务器中证书路径
    ostr_input_data =lf_plain_input_x
  IMPORTING
    ostr_enveloped_data =lf_output
  TABLES
    recipient_list =it_recipient_list
  EXCEPTIONS
    OTHERS =1.

"展示加密后信息
PERFORMshow_dataUSINGlf_output.
SKIP.

CLEARlv_input_x.
CLEARlt_recipient_list.
ls_recipient_list-id ='CN=user1'."一般为证书域名
ls_recipient_list-profile ='/tmp/rsa/user1.pse'.
APPENDls_recipient_listTOlt_recipient_list.

"解密
CALLFUNCTION'SSFW_KRN_DEVELOPE'
  EXPORTING
    str_format ='PKCS1-V1.5'
    ostr_enveloped_data =lf_output
*b_outdec =''
  IMPORTING
    ostr_output_data =lv_input_x
    crc =lv_crc
  TABLES
    recipient =lt_recipient_list
  EXCEPTIONS
    ssf_krn_error =1
    ssf_krn_noop =2
    ssf_krn_nomemory =3
    ssf_krn_opinv =4
    ssf_krn_nossflib =5
    ssf_krn_recipient_error =6
    ssf_krn_input_data_error =7
    ssf_krn_invalid_par =8
    ssf_krn_invalid_parlen =9
    ssf_fb_input_parameter_error =10
    OTHERS =11.

"根据UTF-8格式转为string类型数据
lv_input =cl_bcs_convert=>xstring_to_string(iv_xstr =lv_input_xiv_cp ='4110').

WRITE:/lv_input.

FORM show_data USING data_x TYPE xstring.
  DATA:data_lenTYPEi,
        i1TYPEiVALUE0,
        i2TYPEiVALUE32.
  data_len =xstrlen(data_x).
  WHILE(i1<data_len).
    i2 =nmin(val1 =i2
               val2 =data_len).
    WRITE:/data_x+i1(i2).
    i1 =i1+32.
  ENDWHILE.
ENDFORM.

证书签名验签代码:

DATA: lv_output         TYPE xstring,
      lv_input_x        TYPE xstring,
      lv_input          TYPE string,
      lt_recipient_list TYPE STANDARD TABLE OF ssfinfo,
      ls_recipient_list LIKE LINE OF lt_recipient_list,
      lv_crc            TYPE ssfparms-ssfcrc.

DATA: lf_input_data    TYPE xstring,
          lf_signed_data   TYPE xstring,
          ls_signer        TYPE ssfinfo,          "ssfinfoext is tested by CL_SMIME
          lt_signer        TYPE TABLE OF ssfinfo, "ssfinfoext is tested by CL_SMIME
          lf_crc           TYPE ssfreturn,
          lf_signer_name   TYPE string,
          lf_chain_dataTYPExstring,
          lf_output_dataTYPExstring,
          lf_inputv_dataTYPExstring,
          ls_certificateTYPEssfcertlin,
          lt_certificatesTYPETABLEOFssfcertlin,
          lf_subject(256)TYPEc.

"原文
lv_input ='一段需要处理的报文'.
CALLFUNCTION'SCMS_STRING_TO_XSTRING'
  EXPORTING
    text =lv_input
  IMPORTING
    buffer =lv_input_x.

"签名
ls_signer-id ='CN=user1'."一般为证书域名
ls_signer-profile ='/tmp/rsa/user1.pse'.
APPENDls_signerTOlt_signer.

 CALLFUNCTION'SSFW_KRN_SIGN'
      EXPORTING
        ssftoolkit ='SAPSECULIB'
        str_format ='PKCS1-V1.5'
        b_inc_certs =''
        b_detached ='X'
        b_inenc =''
        str_hashalg ='SHA256'"'SHA1'
        str_chainfmt =''
        ostr_input_data =lv_input_x
      IMPORTING
        ostr_signed_data =lf_signed_data
        str_signer_name =lf_signer_name
*ostr_signer_keyid =
        ostr_chain_data =lf_chain_data
        crc =lf_crc
      TABLES
        signer =lt_signer
      EXCEPTIONS
        ssf_krn_error =1
        ssf_krn_noop =2
        ssf_krn_nomemory =3
        ssf_krn_opinv =4
        ssf_krn_nossflib =5
        ssf_krn_signer_list_error =6
        ssf_krn_input_data_error =7
        ssf_krn_invalid_par =8
        ssf_krn_invalid_parlen =9
        ssf_fb_input_parameter_error =10
        OTHERS =11.

PERFORMshow_dataUSINGlf_signed_data.
SKIP.

lf_inputv_data =lv_input_x.
"验签
CLEARlt_signer.
*verifysignature
    CALLFUNCTION'SSFW_KRN_VERIFY'
      EXPORTING
        ssftoolkit ='SAPSECULIB'
        str_format ='PKCS1-V1.5'
        b_inc_certs =''
        b_inenc =''
        b_outdec =''
        str_pab ='/tmp/rsa/user1.pse'
*str_pab_password =
        ostr_signed_data =lf_signed_data
        ostr_input_data =lf_inputv_data
        str_hashalg ='SHA256'"'SHA1'
        str_chainfmt =''
*ostr_chain_data =''
        str_signer_name =lf_signer_name
*ostr_signer_keyid =
        crl_profile =''
      IMPORTING
        ostr_output_data =lf_output_data
        crc =lf_crc
      TABLES
        signer_result_list =lt_signer
        certificatelist =lt_certificates
      EXCEPTIONS
        ssf_krn_error =1
        ssf_krn_noop =2
        ssf_krn_nomemory =3
        ssf_krn_opinv =4
        ssf_krn_nossflib =5
        ssf_krn_input_data_error =6
        ssf_krn_invalid_par =7
        ssf_krn_invalid_parlen =8
        ssf_fb_input_parameter_error =9
        OTHERS =10.
IFsy-subrc<>0.
*Implementsuitableerrorhandlinghere
  WRITE:/sy-subrc.
ENDIF.


lv_input =cl_bcs_convert=>xstring_to_string(iv_xstr =lf_output_dataiv_cp ='4110').

WRITE:/lv_input.


FORM show_data USING data_x TYPE xstring.
  DATA:data_lenTYPEi
      ,i1TYPEiVALUE0
      ,i2TYPEiVALUE32.
  data_len =xstrlen(data_x).
  WHILE(i1<data_len).
    i2 =nmin(val1 =i2
               val2 =data_len).
    WRITE:/data_x+i1(i2).
    i1 =i1+32.
  ENDWHILE.
ENDFORM.

2、公钥验签

当对方系统收到数据返回时,需要对返回的签名使用对方系统提供的公钥进行验签,证明对方返回的数据没有被篡改

DATA:lv_input_x        TYPE xstring,
      lv_data TYPE string,
      lv_xdata TYPE xstring,
      lv_input          TYPE string,
      lf_signed_data   TYPE xstring,
      lf_signer_name   TYPE string,
      lf_chain_data    TYPE xstring,
      lf_output_data   TYPE xstring,
      ls_signer        TYPE ssfinfo,          "ssfinfoext is tested by CL_SMIME
      lt_signer        TYPE TABLE OF ssfinfo, "ssfinfoext is tested by CL_SMIME
      lt_certificates  TYPE TABLE OF ssfcertlin,
      lf_crc           TYPE ssfreturn.

DATA: lv_base64    TYPE string,
      lv_pubkey    TYPE xstring,
      lv_pubbase64 TYPE string,
      lv_str       TYPE string.

"签名模板 lv_data
= 'json字符串签名模板'. lv_xdata = cl_abap_codepage=>convert_to( source = lv_data codepage = 'UTF-8' ). "签名 lv_input = 'g0r+iYM0FYAHworrKa+XzL/4FO/ci+S0HZr1pJFAf1/Dtkr+pnpFUWM' && 'zf3oHBeJDL3rcCgCqzUOYhpss3MKtOAR/rttdu8a5FQ1G456uYF7D5r'. CALL FUNCTION 'SCMS_BASE64_DECODE_STR' EXPORTING input = lv_input * UNESCAPE = 'X' IMPORTING output = lv_input_x EXCEPTIONS failed = 1 OTHERS = 2. "公钥 lv_pubbase64 = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwFgHD4kzEVPdOj0' && 'YCX+zp5r6R52MY0O7BsTCLT7aHaxsANsvI9ABGx3OaTVlPB59M6GPb'. CALL FUNCTION 'SCMS_BASE64_DECODE_STR' EXPORTING input = lv_pubbase64 * UNESCAPE = 'X' IMPORTING output = lv_pubkey EXCEPTIONS failed = 1 OTHERS = 2. " 模数 lv_str = lv_pubkey. DATA(length) = strlen( lv_str ). IF length > 514." 2048位密钥 DATA(pos) = length - 10 - 514. lv_str = lv_str+pos(514). ELSEIF length > 256." 1024位密钥 pos = length - 10 - 256. lv_str = lv_str+pos(256). ENDIF. " 函数使用密钥格式 lv_str = 'R:m=' && lv_str && ':e=010001:'. lv_pubkey = cl_bcs_convert=>string_to_xstring( lv_str ). CALL FUNCTION 'SSFW_KRN_VERIFY' EXPORTING ssftoolkit = 'SAPSECULIB' str_format = 'PKCS1-V1.5' b_inc_certs = '' b_inenc = '' b_outdec = '' str_pab = '' * str_pab_password = ostr_signed_data = lv_input_x ostr_input_data = lv_xdata str_hashalg = 'SHA1' str_chainfmt = 'KEYVALUE' ostr_chain_data = lv_pubkey * ostr_signer_keyid = crl_profile = '' IMPORTING ostr_output_data = lf_output_data crc = lf_crc TABLES signer_result_list = lt_signer certificatelist = lt_certificates EXCEPTIONS ssf_krn_error = 1 ssf_krn_noop = 2 ssf_krn_nomemory = 3 ssf_krn_opinv = 4 ssf_krn_nossflib = 5 ssf_krn_input_data_error = 6 ssf_krn_invalid_par = 7 ssf_krn_invalid_parlen = 8 ssf_fb_input_parameter_error = 9 OTHERS = 10. IF sy-subrc <> 0. * Implement suitable error handling here WRITE:/ sy-subrc. ENDIF. WRITE:/ lf_crc. lv_input = cl_bcs_convert=>xstring_to_string( iv_xstr = lf_output_data iv_cp = '4110' ). WRITE:/ lv_input.

四、简述调用API过程

使用HTTP方式调用银行API

"创建HTTP对象
CALL METHOD cl_http_client=>create_by_url
    EXPORTING
      url    = ls_ghinfo-zurl
    IMPORTING
      client = lo_http_client.

lo_http_client->propertytype_redirect = lo_http_client->co_disabled .

  CALL METHOD lo_http_client->request->set_header_field
    EXPORTING
      name  = '~request_method'
      value = 'POST'.                 "

  CALL METHOD lo_http_client->request->set_header_field
    EXPORTING
      name  = '~request_protocol'
      value = 'HTTP/1.1'.

  CALL METHOD lo_http_client->request->set_header_field
    EXPORTING
      name  = 'Content-Type'
      value = 'application/x-www-form-urlencoded; charset=UTF-8'.

根据上述签名之后,得到签名字符串拼接到发送报文中

 发送数据并获得返回消息即可

lv_len = strlen( gv_json_send ).

  CALL METHOD lo_http_client->request->set_cdata
    EXPORTING
      data   = gv_json_send
      offset = 0
      length = lv_len.

  CALL METHOD lo_http_client->send
    EXCEPTIONS
      http_communication_failure = 1
      http_invalid_state         = 2.

  CALL METHOD lo_http_client->receive
    EXCEPTIONS
      http_communication_failure = 1
      http_invalid_state         = 2
      http_processing_failed     = 3.

  IF sy-subrc <> 0.
    DATA:l_sysubrc    TYPE sysubrc,
         l_error_text TYPE string.
    l_sysubrc = sy-subrc.
    CALL METHOD lo_http_client->get_last_error
      IMPORTING
        code    = l_sysubrc
        message = l_error_text.
  ENDIF.

    CALL METHOD lo_http_client->response->get_status
    IMPORTING
      code   = http_status_code
      reason = status_text.

    e_message = '调用状态:' && http_status_code && ',状态文本:' && status_text.

  CALL METHOD lo_http_client->response->get_cdata
    RECEIVING data = w_result .

调用时报错

SSL handshake with xxx.com.cn:443 failed: SSSLERR_PEER_CERT_UNTRUSTED (-102)
The peer's X.509 Certificate (chain) is untrusted

SapSSLSessionStartNB()==SSSLERR_PEER_CERT_UNTRUSTED 

 这是因为调用HTTPS地址,没有将网页证书导入到SAP系统。可以在浏览器中下载对应地址的证书,导入SAP系统即可

五、致谢

这是一篇站在前人肩膀上完成的博客,各位前辈的资料,给予了本人极大的帮助,尤其感谢CSDN博主??Fireworks_me??对我疑惑的耐心解答,非常感谢!

参考文档:

??https://blogs.sap.com/2021/04/13/rsa-encryption-in-abap/ ?

??https://blog.csdn.net/xiefireworks/article/details/124809161?spm=1001.2014.3001.5501 ??

??https://blog.csdn.net/somehow1002/article/details/117092352 ???

???https://blog.51cto.com/wushank/1915795???