ABAP中实现RSA加密与签名的两种方法
在SAP系统中,当需要与外部系统进行安全通信时,RSA非对称加密常用于数据加密与数字签名。本文将介绍在ABAP中实现RSA加密与签名的两种方式:一种是基于自定义大数运算类的纯ABAP实现,另一种是使用SAP官方提供的CommonCryptoLib库。两种方法均基于PKCS#1 v1.5填充标准,适用于短消息处理。
方法一:纯ABAP实现(基于大数运算类)
由于ABAP原生不支持大整数运算,我们借助第三方类Z04_BIGINTX实现模幂运算,这是RSA算法的核心。首先,使用OpenSSL生成2048位RSA密钥对:
mkdir -p /tmp/rsa
cd /tmp/rsa
openssl genrsa -out rsa.key 2048
openssl pkey -in rsa.key -text -noout
从输出中提取公钥指数(publicExponent)和模数(modulus),以及私钥指数(privateExponent)。这些值将以十六进制字符串形式嵌入ABAP程序。
以下为完整的ABAP程序,实现消息加密与签名:
REPORT Z_RSA_PURE.
DATA: lv_plain_msg TYPE string VALUE 'Thirty-two bytes secret message!',
lv_encrypted_x TYPE xstring,
lv_signed_x TYPE xstring,
lv_modulus_x TYPE xstring,
lv_priv_exp_x TYPE xstring,
lv_pub_exp_x TYPE xstring,
lv_bigint_256 TYPE REF TO z04_bigintx,
lv_bigint_1 TYPE REF TO z04_bigintx,
lv_bigint_2 TYPE REF TO z04_bigintx,
lv_bigint_msg TYPE REF TO z04_bigintx,
lv_bigint_result TYPE REF TO z04_bigintx,
lv_pub_exponent TYPE REF TO z04_bigintx,
lv_modulus_bi TYPE REF TO z04_bigintx,
lv_priv_exponent TYPE REF TO z04_bigintx,
lv_hex_str TYPE string,
lv_hex_clean TYPE string,
lv_hex_byte TYPE x,
lv_hex_len TYPE i,
lv_index TYPE i,
lv_offset TYPE i,
lv_temp_val TYPE i,
lv_padding_len TYPE i,
lv_pkcs_head_encr TYPE xstring VALUE '0002',
lv_pkcs_head_sign TYPE xstring VALUE '0001',
lv_pkcs_zero TYPE xstring VALUE '00',
lv_pkcs_one TYPE xstring VALUE '01',
lv_pkcs_ff TYPE xstring,
lv_random_bytes TYPE xstring,
lv_file_encrypted TYPE string VALUE '/tmp/rsa/encrypted_msg',
lv_file_signed TYPE string VALUE '/tmp/rsa/signed_msg'.
" 初始化大数对象
CREATE OBJECT lv_bigint_256.
CREATE OBJECT lv_bigint_1.
CREATE OBJECT lv_bigint_2.
CREATE OBJECT lv_bigint_msg.
CREATE OBJECT lv_bigint_result.
CREATE OBJECT lv_pub_exponent.
CREATE OBJECT lv_modulus_bi.
CREATE OBJECT lv_priv_exponent.
lv_bigint_256->seti( 256 ).
" 设置公钥指数(65537)
lv_pub_exponent->setstr( '65537' ).
" 解析私钥指数(十六进制字符串转十进制大数)
lv_hex_str = '00:bd:bb:fe:db:7e:14:48:44:73:29:90:96:24:bf:8a:7e:92:5a:41:a7:06:cd:71:c0:a4:54:5c:43:2e:bf:10:8a:a1:6a:9b:cc:88:d9:6a:4a:29:22:5b:9f:54:24:fc:cc:ca:b0:de:2e:3f:d7:14:46:2b:e9:3c:69:88:cd:2a:2e:df:78:a6:48:e0:50:a3:44:5d:bf:8a:ef:76:bd:6e:f0:5e:4c:24:9c:89:6b:07:a8:1a:be:15:69:e7:ef:5d:9c:b6:e2:8d:93:21:72:c3:ae:91:fc:96:61:a8:9f:11:77:68:be:08:10:c4:6c:3f:de:c8:b2:a3:15:f2:cb:1c:9d:3d:20:75:59:ff:8b:75:9d:44:39:36:84:7e:4d:fe:96:01:29:11:4c:dd:15:33:44:0b:f8:f0:ad:02:62:5f:34:81:fa:73:f9:6c:1d:00:cf:79:0c:71:b8:71:c1:ad:35:84:05:32:95:70:8d:b6:7a:d1:26:fd:2c:32:f4:09:3b:2e:7d:80:18:e2:3e:7d:c6:5b:ad:ad:f0:a7:ff:92:6d:c8:94:34:4a:13:b8:7d:c5:81:02:69:f4:1a:6d:a5:d1:d2:ae:ac:c1:7d:3d:62:97:4a:28:11:07:60:b1:3d:02:ba:5a:98:9b:49:60:fb:65:03:65:d2:be:c7:c1:aa:41'.
lv_hex_clean = lv_hex_str.
REPLACE ALL OCCURRENCES OF ':' IN lv_hex_clean WITH ''.
TRANSLATE lv_hex_clean TO UPPER CASE.
lv_priv_exp_x = lv_hex_clean.
lv_bigint_1->seti( 1 ).
lv_hex_len = xstrlen( lv_priv_exp_x ).
lv_index = lv_hex_len.
WHILE lv_index > 0.
lv_index = lv_index - 1.
lv_temp_val = lv_priv_exp_x+lv_index(1).
lv_bigint_2->seti( lv_temp_val ).
lv_bigint_2->mul( lv_bigint_1 ).
lv_priv_exponent->add( lv_bigint_2 ).
lv_bigint_1->mul( lv_bigint_256 ).
ENDWHILE.
" 解析模数(公钥模数)
lv_hex_str = '00:e2:05:b9:ed:b7:8c:24:f3:4e:d0:07:38:74:81:dc:30:5c:5c:b1:7b:5f:da:e3:57:43:65:e7:a4:83:10:b9:a7:ae:3d:07:5a:53:e8:5b:7b:3f:a7:92:99:9a:83:75:9e:ad:d3:82:69:cc:07:72:1e:3c:e3:d8:33:0f:b7:a5:69:02:45:f2:69:04:97:2c:77:7b:74:a5:98:9f:0d:9b:fd:a9:16:a6:d7:7b:eb:5c:f1:f6:82:18:d6:d1:f3:d3:2e:15:6d:f9:17:d3:2b:7b:17:0b:cf:8d:42:ba:5d:fa:5c:c9:8c:a6:27:52:8c:98:6f:d4:86:c2:04:34:bc:61:69:4e:7a:fe:09:ca:76:5d:35:5d:37:0a:7b:e2:64:06:9b:f4:d9:da:70:f9:bc:3a:04:4d:f8:db:51:57:16:64:a9:a1:db:80:1c:0f:3f:96:6f:bd:53:a3:ea:76:fc:54:e8:80:c1:d7:a9:f9:5f:be:08:c9:40:93:42:c3:16:ed:74:20:6b:0d:dc:a9:55:93:62:28:14:b1:91:5b:1c:f1:1d:75:f9:02:64:33:5f:4a:87:66:45:df:96:65:3b:82:46:30:fa:f8:af:fd:f8:ad:63:ec:a9:38:6b:62:cc:0b:ba:f4:6c:f3:88:61:ca:d6:fb:45:42:fb:29:22:cf:c4:13'.
lv_hex_clean = lv_hex_str.
REPLACE ALL OCCURRENCES OF ':' IN lv_hex_clean WITH ''.
TRANSLATE lv_hex_clean TO UPPER CASE.
lv_modulus_x = lv_hex_clean.
lv_bigint_1->seti( 1 ).
lv_hex_len = xstrlen( lv_modulus_x ).
lv_index = lv_hex_len.
WHILE lv_index > 0.
lv_index = lv_index - 1.
lv_temp_val = lv_modulus_x+lv_index(1).
lv_bigint_2->seti( lv_temp_val ).
lv_bigint_2->mul( lv_bigint_1 ).
lv_modulus_bi->add( lv_bigint_2 ).
lv_bigint_1->mul( lv_bigint_256 ).
ENDWHILE.
" 准备明文消息
CALL FUNCTION 'SCMS_STRING_TO_XSTRING'
EXPORTING
text = lv_plain_msg
IMPORTING
buffer = lv_encrypted_x.
WRITE: / '原始消息(HEX):'.
PERFORM DISPLAY_HEX USING lv_encrypted_x.
" PKCS#1 v1.5 填充 - 加密
lv_padding_len = 256 - 3 - xstrlen( lv_encrypted_x ). " 2048位密钥,减去头部和分隔符
lv_pkcs_ff = ''. " 生成填充字节 FF
DO lv_padding_len TIMES.
CONCATENATE lv_pkcs_ff 'FF' INTO lv_pkcs_ff IN BYTE MODE.
ENDDO.
" 生成随机填充(非零字节)
CALL FUNCTION 'GENERATE_SEC_RANDOM'
EXPORTING
length = lv_padding_len - 1
IMPORTING
random = lv_random_bytes
EXCEPTIONS
OTHERS = 1.
REPLACE ALL OCCURRENCES OF lv_pkcs_zero IN lv_random_bytes WITH lv_pkcs_one IN BYTE MODE.
CONCATENATE lv_pkcs_head_encr lv_random_bytes lv_pkcs_zero lv_encrypted_x INTO lv_encrypted_x IN BYTE MODE.
WRITE: / '加密前填充后(HEX):'.
PERFORM DISPLAY_HEX USING lv_encrypted_x.
" 转换为十进制大整数进行模幂运算
lv_bigint_msg->seti( 0 ).
lv_hex_len = xstrlen( lv_encrypted_x ).
lv_bigint_1->seti( 1 ).
lv_index = lv_hex_len.
WHILE lv_index > 0.
lv_index = lv_index - 1.
lv_temp_val = lv_encrypted_x+lv_index(1).
lv_bigint_2->seti( lv_temp_val ).
lv_bigint_2->mul( lv_bigint_1 ).
lv_bigint_msg->add( lv_bigint_2 ).
lv_bigint_1->mul( lv_bigint_256 ).
ENDWHILE.
" 执行加密:c = m^e mod n
GET RUN TIME FIELD lv_offset.
lv_bigint_result = lv_bigint_msg->powmod(
x = lv_pub_exponent
m = lv_modulus_bi ).
GET RUN TIME FIELD lv_temp_val.
WRITE: / '加密耗时(毫秒):', lv_temp_val - lv_offset.
" 转换结果回十六进制
lv_encrypted_x = ''.
lv_bigint_1->seti( 1 ).
DO.
lv_bigint_2->setobj( lv_bigint_result )->mod( lv_bigint_256 ).
lv_temp_val = lv_bigint_2->getstr( 0 ).
lv_hex_byte = lv_temp_val.
CONCATENATE lv_hex_byte lv_encrypted_x INTO lv_encrypted_x IN BYTE MODE.
lv_bigint_result = lv_bigint_result->div( lv_bigint_256 ).
IF lv_bigint_result->getstr( 0 ) = '0'.
EXIT.
ENDIF.
ENDDO.
" 写入加密文件
OPEN DATASET lv_file_encrypted FOR OUTPUT IN BINARY MODE.
TRANSFER lv_encrypted_x TO lv_file_encrypted.
CLOSE DATASET lv_file_encrypted.
WRITE: / '加密结果(HEX):'.
PERFORM DISPLAY_HEX USING lv_encrypted_x.
" PKCS#1 v1.5 填充 - 签名
CONCATENATE lv_pkcs_head_sign lv_pkcs_ff lv_pkcs_zero lv_plain_msg INTO lv_signed_x IN BYTE MODE.
WRITE: / '签名前填充后(HEX):'.
PERFORM DISPLAY_HEX USING lv_signed_x.
" 转换为十进制大整数
lv_bigint_msg->seti( 0 ).
lv_hex_len = xstrlen( lv_signed_x ).
lv_bigint_1->seti( 1 ).
lv_index = lv_hex_len.
WHILE lv_index > 0.
lv_index = lv_index - 1.
lv_temp_val = lv_signed_x+lv_index(1).
lv_bigint_2->seti( lv_temp_val ).
lv_bigint_2->mul( lv_bigint_1 ).
lv_bigint_msg->add( lv_bigint_2 ).
lv_bigint_1->mul( lv_bigint_256 ).
ENDWHILE.
" 执行签名:s = m^d mod n
GET RUN TIME FIELD lv_offset.
lv_bigint_result = lv_bigint_msg->powmod(
x = lv_priv_exponent
m = lv_modulus_bi ).
GET RUN TIME FIELD lv_temp_val.
WRITE: / '签名耗时(毫秒):', lv_temp_val - lv_offset.
" 转换结果回十六进制
lv_signed_x = ''.
DO.
lv_bigint_2->setobj( lv_bigint_result )->mod( lv_bigint_256 ).
lv_temp_val = lv_bigint_2->getstr( 0 ).
lv_hex_byte = lv_temp_val.
CONCATENATE lv_hex_byte lv_signed_x INTO lv_signed_x IN BYTE MODE.
lv_bigint_result = lv_bigint_result->div( lv_bigint_256 ).
IF lv_bigint_result->getstr( 0 ) = '0'.
EXIT.
ENDIF.
ENDDO.
" 写入签名文件
OPEN DATASET lv_file_signed FOR OUTPUT IN BINARY MODE.
TRANSFER lv_signed_x TO lv_file_signed.
CLOSE DATASET lv_file_signed.
WRITE: / '签名结果(HEX):'.
PERFORM DISPLAY_HEX USING lv_signed_x.
FORM DISPLAY_HEX USING pv_xstr TYPE xstring.
DATA: lv_len TYPE i, lv_pos TYPE i, lv_chunk TYPE i.
lv_len = xstrlen( pv_xstr ).
lv_pos = 0.
lv_chunk = 32.
WHILE lv_pos < lv_len.
lv_chunk = min( lv_chunk, lv_len - lv_pos ).
WRITE: / pv_xstr+lv_pos(lv_chunk).
lv_pos = lv_pos + 32.
ENDWHILE.
ENDFORM.
方法二:使用CommonCryptoLib(推荐生产环境)
SAP官方提供的CommonCryptoLib(CCL)封装了高性能加密算法,支持PKCS#1、PKCS#7等标准,是生产系统的首选方案。
首先,将生成的RSA密钥转换为PSE格式供ABAP使用:
openssl req -x509 -sha256 -key rsa.key -out user1.cer -days 3650 -subj '/CN=user1'
openssl pkcs12 -export -inkey rsa.key -in user1.cer -out user1.pfx -nodes
export SECUDIR=$PWD
sapgenpse import_p12 -x "" -p user1.pse user1.pfx
然后编写ABAP程序进行加密与签名:
REPORT Z_RSA_CCL.
DATA: lv_plain_text TYPE string VALUE 'Thirty-two bytes secret message.',
lv_plain_x TYPE xstring,
lv_encrypted_x TYPE xstring,
lv_signed_x TYPE xstring,
lv_recipient_list TYPE STANDARD TABLE OF ssfinfo,
lv_recipient TYPE ssfinfo,
lv_signer_list TYPE STANDARD TABLE OF ssfinfo,
lv_signer TYPE ssfinfo,
lv_start_time TYPE i,
lv_end_time TYPE i,
lv_duration TYPE i,
lv_file_encrypted TYPE string VALUE '/tmp/rsa/ccl_encrypted_msg',
lv_file_signed TYPE string VALUE '/tmp/rsa/ccl_signed_msg'.
" 转换明文为XSTRING
CALL FUNCTION 'SCMS_STRING_TO_XSTRING'
EXPORTING
text = lv_plain_text
IMPORTING
buffer = lv_plain_x.
" --- 加密 ---
lv_recipient-id = 'CN=user1'.
APPEND lv_recipient TO lv_recipient_list.
GET RUN TIME FIELD lv_start_time.
CALL FUNCTION 'SSFW_KRN_ENVELOPE'
EXPORTING
str_format = 'PKCS1-V1.5'
str_pab = '/tmp/rsa/user1.pse'
ostr_input_data = lv_plain_x
IMPORTING
ostr_enveloped_data = lv_encrypted_x
TABLES
recipient_list = lv_recipient_list
EXCEPTIONS
OTHERS = 1.
GET RUN TIME FIELD lv_end_time.
lv_duration = lv_end_time - lv_start_time.
WRITE: / 'CCL加密耗时(毫秒):', lv_duration.
" 写入加密文件
OPEN DATASET lv_file_encrypted FOR OUTPUT IN BINARY MODE.
TRANSFER lv_encrypted_x TO lv_file_encrypted.
CLOSE DATASET lv_file_encrypted.
WRITE: / 'CCL加密结果(HEX):'.
PERFORM DISPLAY_HEX USING lv_encrypted_x.
" --- 签名 ---
lv_signer-id = 'CN=user1'.
lv_signer-profile = '/tmp/rsa/user1.pse'.
APPEND lv_signer TO lv_signer_list.
GET RUN TIME FIELD lv_start_time.
CALL FUNCTION 'SSFW_KRN_SIGN'
EXPORTING
str_format = 'PKCS1-V1.5'
str_chainfmt = 'X509v3'
str_hashalg = 'SHA256'
ostr_input_data = lv_plain_x
IMPORTING
ostr_signed_data = lv_signed_x
TABLES
signer = lv_signer_list
EXCEPTIONS
OTHERS = 1.
GET RUN TIME FIELD lv_end_time.
lv_duration = lv_end_time - lv_start_time.
WRITE: / 'CCL签名耗时(毫秒):', lv_duration.
" 写入签名文件
OPEN DATASET lv_file_signed FOR OUTPUT IN BINARY MODE.
TRANSFER lv_signed_x TO lv_file_signed.
CLOSE DATASET lv_file_signed.
WRITE: / 'CCL签名结果(HEX):'.
PERFORM DISPLAY_HEX USING lv_signed_x.
FORM DISPLAY_HEX USING pv_xstr TYPE xstring.
DATA: lv_len TYPE i, lv_pos TYPE i, lv_chunk TYPE i.
lv_len = xstrlen( pv_xstr ).
lv_pos = 0.
lv_chunk = 32.
WHILE lv_pos < lv_len.
lv_chunk = min( lv_chunk, lv_len - lv_pos ).
WRITE: / pv_xstr+lv_pos(lv_chunk).
lv_pos = lv_pos + 32.
ENDWHILE.
ENDFORM.
验证签名与解密时,可使用OpenSSL命令行工具:
- 解密:
openssl rsautl -decrypt -in ccl_encrypted_msg -inkey rsa.key - 验证签名:
openssl rsautl -verify -in ccl_signed_msg -inkey rsa.key
注意:CCL签名输出包含ASN.1结构,若需验证原始数据,应使用-raw参数并比对SHA-256哈希值。
性能与安全建议
- 纯ABAP实现仅用于教学目的,执行效率极低(签名耗时可达数十秒),且易受侧信道攻击。
- 生产系统必须使用CommonCryptoLib,其底层使用C/C++优化实现,支持硬件加速。
- 密钥管理应通过STRUST事务码配置,避免硬编码路径。
- 对于长消息,应先计算SHA-256摘要,再对摘要签名,而非直接对消息进行RSA运算。