当前位置:首页 > 技术 > 正文内容

ABAP中实现RSA加密与签名的两种方法

访客 技术 2026年10月7日 1

在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运算。
标签: ABAP

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。