epics 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +6 -0
  5. data/CONTRIBUTING.md +5 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +165 -0
  8. data/README.md +197 -0
  9. data/Rakefile +7 -0
  10. data/epics.gemspec +50 -0
  11. data/lib/epics.rb +35 -0
  12. data/lib/epics/cct.rb +42 -0
  13. data/lib/epics/cd1.rb +42 -0
  14. data/lib/epics/cdd.rb +42 -0
  15. data/lib/epics/client.rb +216 -0
  16. data/lib/epics/error.rb +324 -0
  17. data/lib/epics/generic_request.rb +99 -0
  18. data/lib/epics/generic_upload_request.rb +89 -0
  19. data/lib/epics/haa.rb +40 -0
  20. data/lib/epics/hia.rb +78 -0
  21. data/lib/epics/hpb.rb +29 -0
  22. data/lib/epics/hpd.rb +40 -0
  23. data/lib/epics/htd.rb +40 -0
  24. data/lib/epics/ini.rb +69 -0
  25. data/lib/epics/key.rb +79 -0
  26. data/lib/epics/mgf.rb +41 -0
  27. data/lib/epics/middleware/parse_ebics.rb +15 -0
  28. data/lib/epics/middleware/xmlsig.rb +18 -0
  29. data/lib/epics/ptk.rb +52 -0
  30. data/lib/epics/response.rb +93 -0
  31. data/lib/epics/signer.rb +40 -0
  32. data/lib/epics/sta.rb +52 -0
  33. data/lib/epics/version.rb +3 -0
  34. data/lib/letter/ini.erb +231 -0
  35. data/spec/client_spec.rb +98 -0
  36. data/spec/fixtures/a006.pem +28 -0
  37. data/spec/fixtures/bank_e.pem +6 -0
  38. data/spec/fixtures/e002.pem +28 -0
  39. data/spec/fixtures/x002.pem +28 -0
  40. data/spec/fixtures/xml/cd1.xml +87 -0
  41. data/spec/fixtures/xml/ebics_business_nok.xml +21 -0
  42. data/spec/fixtures/xml/ebics_technical_nok.xml +12 -0
  43. data/spec/fixtures/xml/hia.xml +2 -0
  44. data/spec/fixtures/xml/hia_request_order_data.xml +2 -0
  45. data/spec/fixtures/xml/hpb.xml +34 -0
  46. data/spec/fixtures/xml/hpb_request.xml +34 -0
  47. data/spec/fixtures/xml/hpb_response.xml +21 -0
  48. data/spec/fixtures/xml/hpb_response_order.xml +22 -0
  49. data/spec/fixtures/xml/htd_order_data.xml +153 -0
  50. data/spec/fixtures/xml/ini.xml +2 -0
  51. data/spec/fixtures/xml/signature_pub_key_order_data.xml +2 -0
  52. data/spec/fixtures/xml/upload_init_response.xml +31 -0
  53. data/spec/hpb_spec.rb +15 -0
  54. data/spec/key_spec.rb +35 -0
  55. data/spec/mgf_spec.rb +36 -0
  56. data/spec/middleware/parse_ebics_spec.rb +18 -0
  57. data/spec/orders/cct_spec.rb +17 -0
  58. data/spec/orders/cd1_spec.rb +17 -0
  59. data/spec/orders/cdd_spec.rb +17 -0
  60. data/spec/orders/haa_spec.rb +11 -0
  61. data/spec/orders/hia_spec.rb +34 -0
  62. data/spec/orders/hpb_spec.rb +11 -0
  63. data/spec/orders/hpd_spec.rb +11 -0
  64. data/spec/orders/htd_spec.rb +11 -0
  65. data/spec/orders/ini_spec.rb +36 -0
  66. data/spec/orders/ptk_spec.rb +11 -0
  67. data/spec/orders/sta_spec.rb +11 -0
  68. data/spec/response_spec.rb +34 -0
  69. data/spec/signer_spec.rb +34 -0
  70. data/spec/spec_helper.rb +43 -0
  71. data/spec/support/ebics_matcher.rb +22 -0
  72. data/spec/xsd/ebics_H004.xsd +11 -0
  73. data/spec/xsd/ebics_hev.xsd +135 -0
  74. data/spec/xsd/ebics_keymgmt_request_H004.xsd +543 -0
  75. data/spec/xsd/ebics_keymgmt_response_H004.xsd +137 -0
  76. data/spec/xsd/ebics_orders_H004.xsd +1892 -0
  77. data/spec/xsd/ebics_request_H004.xsd +355 -0
  78. data/spec/xsd/ebics_response_H004.xsd +166 -0
  79. data/spec/xsd/ebics_signature.xsd +217 -0
  80. data/spec/xsd/ebics_types_H004.xsd +2426 -0
  81. data/spec/xsd/xmldsig-core-schema.xsd +318 -0
  82. metadata +319 -0
data/lib/epics/key.rb ADDED
@@ -0,0 +1,79 @@
1
+ class Epics::Key
2
+ attr_accessor :key
3
+
4
+ def initialize(encoded_key, passphrase = nil)
5
+ if encoded_key.kind_of?(OpenSSL::PKey::RSA)
6
+ self.key = encoded_key
7
+ else
8
+ self.key = OpenSSL::PKey::RSA.new(encoded_key)
9
+ end
10
+ end
11
+
12
+ ###
13
+ # concat the exponent and modulus (hex representation) with a single whitespace
14
+ # remove leading zeros from both
15
+ # calculate digest (SHA256)
16
+ # encode as Base64
17
+ ####
18
+ def public_digest
19
+ c = [ e.gsub(/^0*/,''), n.gsub(/^0*/,'') ].map(&:downcase).join(" ")
20
+
21
+ Base64.encode64(digester.digest(c)).strip
22
+ end
23
+
24
+ def n
25
+ self.key.n.to_s(16)
26
+ end
27
+
28
+ def e
29
+ self.key.e.to_s(16)
30
+ end
31
+
32
+ def sign(msg, salt = OpenSSL::Random.random_bytes(32) )
33
+ Base64.encode64(mod_pow(OpenSSL::BN.new(emsa_pss(msg, salt).to_s, 2), self.key.d, self.key.n).to_s(2)).gsub("\n", "")
34
+ end
35
+
36
+ def recover(msg)
37
+ mod_pow(OpenSSL::BN.new(msg.to_s, 2), self.key.e, self.key.n).to_s(2)
38
+ end
39
+
40
+ def digester
41
+ @digester ||= OpenSSL::Digest::SHA256.new
42
+ end
43
+
44
+ private
45
+
46
+ ##
47
+ # http://de.wikipedia.org/wiki/Probabilistic_Signature_Scheme
48
+ ##
49
+ def emsa_pss(msg, salt)
50
+ m_tick_hash = digester.digest [("\x00" * 8), digester.digest(msg), salt].join
51
+
52
+ ps = "\x00" * 190
53
+ db = [ps, "\x01", salt].join
54
+
55
+ db_mask = Epics::MGF1.new.generate(m_tick_hash, db.size)
56
+ masked_db = Epics::MGF1.new.xor(db, db_mask)
57
+
58
+ masked_db_msb = OpenSSL::BN.new(masked_db[0], 2).to_i.to_s(2).rjust(8, "0")
59
+ masked_db_msb[0] = "0"
60
+
61
+ masked_db[0] = OpenSSL::BN.new(masked_db_msb.to_i(2).to_s).to_s(2)
62
+
63
+ [masked_db, m_tick_hash, ["BC"].pack("H*") ].join
64
+ end
65
+
66
+ def mod_pow(base, power, mod)
67
+ base = base.to_i
68
+ power = power.to_i
69
+ mod = mod.to_i
70
+ result = 1
71
+ while power > 0
72
+ result = (result * base) % mod if power & 1 == 1
73
+ base = (base * base) % mod
74
+ power >>= 1
75
+ end
76
+ OpenSSL::BN.new(result.to_s)
77
+ end
78
+
79
+ end
data/lib/epics/mgf.rb ADDED
@@ -0,0 +1,41 @@
1
+ class Epics::MGF1
2
+ def initialize(digest = OpenSSL::Digest::SHA256)
3
+ @digest = digest.new
4
+ @hlen = 32
5
+ end
6
+
7
+ def generate(seed, masklen)
8
+ if masklen > (2 << 31) * @hlen
9
+ raise ArgumentError, "mask too long"
10
+ end
11
+ t = ""
12
+ divceil(masklen, @hlen).times do |counter|
13
+ t += @digest.digest(seed + i2osp(counter, 4))
14
+ end
15
+ t[0, masklen]
16
+ end
17
+
18
+ def i2osp(x, len)
19
+ if x >= 256 ** len
20
+ raise ArgumentError, "integer too large"
21
+ end
22
+ [x].pack("N").gsub(/^\x00+/, '').rjust(len, "\x00")
23
+ end
24
+
25
+ def divceil(a, b)
26
+ (a + b - 1) / b
27
+ end
28
+
29
+ def xor(a, b)
30
+ if a.size != b.size
31
+ raise ArgumentError, "different length for a and b"
32
+ end
33
+ a = a.unpack('C*')
34
+ b = b.unpack('C*')
35
+ a.size.times do |idx|
36
+ a[idx] ^= b[idx]
37
+ end
38
+ a.pack("C*")
39
+ end
40
+
41
+ end
@@ -0,0 +1,15 @@
1
+ class Epics::ParseEbics < Faraday::Middleware
2
+
3
+ def initialize(app = nil, options = {})
4
+ super(app)
5
+ @client = options[:client]
6
+ end
7
+
8
+ def call(env)
9
+ @app.call(env).on_complete do |env|
10
+ env[:body] = ::Epics::Response.new(@client, env[:body])
11
+ raise Epics::Error::TechnicalError.new(env[:body].technical_code) if env[:body].technical_error?
12
+ raise Epics::Error::BusinessError.new(env[:body].business_code) if env[:body].business_error?
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ class Epics::XMLSIG < Faraday::Middleware
2
+
3
+ def initialize(app, options = {})
4
+ super(app)
5
+ @client = options[:client]
6
+ end
7
+
8
+ def call(env)
9
+ @signer = Epics::Signer.new(@client, env["body"])
10
+ @signer.digest!
11
+ @signer.sign!
12
+
13
+ env["body"] = @signer.doc.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML)
14
+
15
+ @app.call(env)
16
+ end
17
+
18
+ end
data/lib/epics/ptk.rb ADDED
@@ -0,0 +1,52 @@
1
+ class Epics::PTK < Epics::GenericRequest
2
+ attr_accessor :from, :to
3
+
4
+ def initialize(client, from, to)
5
+ super(client)
6
+ self.from = from
7
+ self.to = to
8
+ end
9
+
10
+ def header
11
+ {
12
+ :@authenticate => true,
13
+ static: {
14
+ "HostID" => host_id,
15
+ "Nonce" => nonce,
16
+ "Timestamp" => timestamp,
17
+ "PartnerID" => partner_id,
18
+ "UserID" => user_id,
19
+ "Product" => {
20
+ :@Language => "de",
21
+ :content! => "EPICS - a ruby ebics kernel"
22
+ },
23
+ "OrderDetails" => {
24
+ "OrderType" => "PTK",
25
+ "OrderAttribute" => "DZHNN",
26
+ "StandardOrderParams" => {
27
+ "DateRange" => {
28
+ "Start" => from,
29
+ "End" => to
30
+ }
31
+ }
32
+ },
33
+ "BankPubKeyDigests" => {
34
+ "Authentication" => {
35
+ :@Version => "X002",
36
+ :@Algorithm => "http://www.w3.org/2001/04/xmlenc#sha256",
37
+ :content! => client.bank_x.public_digest
38
+ },
39
+ "Encryption" => {
40
+ :@Version => "E002",
41
+ :@Algorithm => "http://www.w3.org/2001/04/xmlenc#sha256",
42
+ :content! => client.bank_e.public_digest
43
+ }
44
+ },
45
+ "SecurityMedium" => "0000"
46
+ },
47
+ "mutable" => {
48
+ "TransactionPhase" => "Initialisation"
49
+ }
50
+ }
51
+ end
52
+ end
@@ -0,0 +1,93 @@
1
+ class Epics::Response
2
+ attr_accessor :doc
3
+ attr_accessor :client
4
+
5
+ def initialize(client, xml)
6
+ self.doc = Nokogiri::XML.parse(xml)
7
+ self.client = client
8
+ end
9
+
10
+ def technical_error?
11
+ technical_code != "000000"
12
+ end
13
+
14
+ def technical_code
15
+ doc.xpath("//xmlns:header/xmlns:mutable/xmlns:ReturnCode").text
16
+ end
17
+
18
+ def business_error?
19
+ business_code != "000000" && business_code != ""
20
+ end
21
+
22
+ def business_code
23
+ doc.xpath("//xmlns:body/xmlns:ReturnCode").text
24
+ end
25
+
26
+ def ok?
27
+ !technical_error? & !business_error?
28
+ end
29
+
30
+ def return_code
31
+ doc.xpath("//xmlns:ReturnCode").last.content
32
+ rescue NoMethodError
33
+ nil
34
+ end
35
+
36
+ def report_text
37
+ doc.xpath("//xmlns:ReportText").first.content
38
+ end
39
+
40
+ def transaction_id
41
+ doc.xpath("//xmlns:TransactionID").first.content
42
+ end
43
+
44
+ def digest_valid?
45
+ authenticated = doc.xpath("//*[@authenticate='true']").map(&:canonicalize).join
46
+ digest_value = doc.xpath("//ds:DigestValue").first
47
+
48
+ digest = Base64.encode64(digester.digest(authenticated)).strip
49
+
50
+ digest == digest_value.content
51
+ end
52
+
53
+ def signature_valid?
54
+ signature = doc.xpath("//ds:SignedInfo").first.canonicalize
55
+ signature_value = doc.xpath("//ds:SignatureValue").first
56
+
57
+ client.bank_x.key.verify(digester, Base64.decode64(signature_value.content), signature)
58
+ end
59
+
60
+ def public_digest_valid?
61
+ encryption_pub_key_digest = doc.xpath("//xmlns:EncryptionPubKeyDigest").first
62
+
63
+ client.e.public_digest == encryption_pub_key_digest.content
64
+ end
65
+
66
+ def order_data
67
+ order_data_encrypted = Base64.decode64(doc.xpath("//xmlns:OrderData").first.content)
68
+
69
+ data = (cipher.update(order_data_encrypted) + cipher.final)
70
+
71
+ Zlib::Inflate.new.inflate(data)
72
+ end
73
+
74
+ def cipher
75
+ cipher = OpenSSL::Cipher::Cipher.new("aes-128-cbc")
76
+
77
+ cipher.decrypt
78
+ cipher.padding = 0
79
+ cipher.key = transaction_key
80
+ cipher
81
+ end
82
+
83
+ def transaction_key
84
+ transaction_key_encrypted = Base64.decode64(doc.xpath("//xmlns:TransactionKey").first.content)
85
+
86
+ @transaction_key ||= client.e.key.private_decrypt(transaction_key_encrypted)
87
+ end
88
+
89
+ def digester
90
+ @digester ||= OpenSSL::Digest::SHA256.new
91
+ end
92
+
93
+ end
@@ -0,0 +1,40 @@
1
+ class Epics::Signer
2
+ attr_accessor :doc, :client
3
+
4
+ def initialize(client, doc = nil)
5
+ self.doc = Nokogiri::XML.parse(doc) if doc
6
+ self.client = client
7
+ end
8
+
9
+ def digest!
10
+ content_to_digest = Base64.encode64(digester.digest(doc.xpath("//*[@authenticate='true']").map(&:canonicalize).join)).strip
11
+
12
+ if digest_node
13
+ digest_node.content = content_to_digest
14
+ end
15
+
16
+ doc
17
+ end
18
+
19
+ def sign!
20
+ signature_value_node = doc.xpath("//ds:SignatureValue").first
21
+
22
+ if signature_node
23
+ signature_value_node.content = Base64.encode64(client.x.key.sign(digester, signature_node.canonicalize)).gsub(/\n/,'')
24
+ end
25
+
26
+ doc
27
+ end
28
+
29
+ def digest_node
30
+ @d ||= doc.xpath("//ds:DigestValue").first
31
+ end
32
+
33
+ def signature_node
34
+ @s ||= doc.xpath("//ds:SignedInfo").first
35
+ end
36
+
37
+ def digester
38
+ OpenSSL::Digest::SHA256.new
39
+ end
40
+ end
data/lib/epics/sta.rb ADDED
@@ -0,0 +1,52 @@
1
+ class Epics::STA < Epics::GenericRequest
2
+ attr_accessor :from, :to
3
+
4
+ def initialize(client, from, to)
5
+ super(client)
6
+ self.from = from
7
+ self.to = to
8
+ end
9
+
10
+ def header
11
+ {
12
+ :@authenticate => true,
13
+ static: {
14
+ "HostID" => host_id,
15
+ "Nonce" => nonce,
16
+ "Timestamp" => timestamp,
17
+ "PartnerID" => partner_id,
18
+ "UserID" => user_id,
19
+ "Product" => {
20
+ :@Language => "de",
21
+ :content! => "EPICS - a ruby ebics kernel"
22
+ },
23
+ "OrderDetails" => {
24
+ "OrderType" => "STA",
25
+ "OrderAttribute" => "DZHNN",
26
+ "StandardOrderParams" => {
27
+ "DateRange" => {
28
+ "Start" => from,
29
+ "End" => to
30
+ }
31
+ }
32
+ },
33
+ "BankPubKeyDigests" => {
34
+ "Authentication" => {
35
+ :@Version => "X002",
36
+ :@Algorithm => "http://www.w3.org/2001/04/xmlenc#sha256",
37
+ :content! => client.bank_x.public_digest
38
+ },
39
+ "Encryption" => {
40
+ :@Version => "E002",
41
+ :@Algorithm => "http://www.w3.org/2001/04/xmlenc#sha256",
42
+ :content! => client.bank_e.public_digest
43
+ }
44
+ },
45
+ "SecurityMedium" => "0000"
46
+ },
47
+ "mutable" => {
48
+ "TransactionPhase" => "Initialisation"
49
+ }
50
+ }
51
+ end
52
+ end
@@ -0,0 +1,3 @@
1
+ module Epics
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,231 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
3
+ <head>
4
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
5
+ <meta charset="UTF-8" />
6
+ <title>EBICS ini</title>
7
+ </head>
8
+ <body>
9
+ <div>
10
+ <h2>EBICS-Initialisierungsbrief (INI)</h2>
11
+ <table>
12
+ <tr>
13
+ <td>
14
+ Datum
15
+ </td>
16
+ <td>
17
+ <%= Date.today.strftime("%d.%m.%Y") %>
18
+ </td>
19
+ </tr>
20
+ <tr>
21
+ <td>
22
+ Uhrzeit
23
+ </td>
24
+ <td>
25
+ <%= Time.now.strftime("%H:%M:%S") %>
26
+ </td>
27
+ </tr>
28
+ <tr>
29
+ <td>
30
+ Empfänger
31
+ </td>
32
+ <td>
33
+ <%= bankname %>
34
+ </td>
35
+ </tr>
36
+ <tr>
37
+ <td>
38
+ User-ID
39
+ </td>
40
+ <td>
41
+ <%= user_id %>
42
+ </td>
43
+ </tr>
44
+ <tr>
45
+ <td>
46
+ Kunden-ID
47
+ </td>
48
+ <td>
49
+ <%= partner_id %>
50
+ </td>
51
+ </tr>
52
+ </table>
53
+ <p>Öffentlicher Schlüssel für die elektronische Unterschrift (A006)</p>
54
+ <p>Exponent (<%= a.key.e.num_bytes * 8 %> Bit):</p>
55
+ <p><code><%= a.e %></code></p>
56
+ <p>Modulus (<%= a.key.n.num_bytes * 8 %> Bit):</p>
57
+ <p><code><%= a.n.scan(/.{2}/).join(" ") %></code></p>
58
+ <p>Hash (SHA-256):</p>
59
+ <p>
60
+ <code><%= Base64.decode64(a.public_digest).unpack("H*").join.upcase.scan(/.{2}/).join(" ") %></code>
61
+ </p>
62
+ <p>Ich bestätige hiermit den obigen öffentlichen Schlüssel für meine elektronische Unterschrift.</p>
63
+ <br/>
64
+ <br/>
65
+ <br/>
66
+ <br/>
67
+ <table>
68
+ <tr>
69
+ <td>
70
+ _________________________
71
+ </td>
72
+ <td>
73
+ _________________________
74
+ </td>
75
+ <td>
76
+ _________________________
77
+ </td>
78
+ </tr>
79
+ <tr>
80
+ <td>
81
+ Ort/Datum
82
+ </td>
83
+ <td>
84
+ Name/Firma
85
+ </td>
86
+ <td>
87
+ Unterschrift
88
+ </td>
89
+ </tr>
90
+ </table>
91
+ </div>
92
+ <div style="page-break-after:always"></div>
93
+ <h2>EBICS-Initialisierungsbrief (HIA) - Seite 1/2</h2>
94
+ <table>
95
+ <tr>
96
+ <td>
97
+ Datum
98
+ </td>
99
+ <td>
100
+ <%= Date.today.strftime("%d.%m.%Y") %>
101
+ </td>
102
+ </tr>
103
+ <tr>
104
+ <td>
105
+ Uhrzeit
106
+ </td>
107
+ <td>
108
+ <%= Time.now.strftime("%H:%M:%S") %>
109
+ </td>
110
+ </tr>
111
+ <tr>
112
+ <td>
113
+ Empfänger
114
+ </td>
115
+ <td>
116
+ <%= bankname %>
117
+ </td>
118
+ </tr>
119
+ <tr>
120
+ <td>
121
+ User-ID
122
+ </td>
123
+ <td>
124
+ <%= user_id %>
125
+ </td>
126
+ </tr>
127
+ <tr>
128
+ <td>
129
+ Kunden-ID
130
+ </td>
131
+ <td>
132
+ <%= partner_id %>
133
+ </td>
134
+ </tr>
135
+ </table>
136
+ <div>
137
+ <p>Öffentlicher Authentifikationsschlüssel (X002)</p>
138
+ <p>Exponent (<%= x.key.e.num_bytes * 8 %> Bit):</p>
139
+ <p><code><%= x.e %></code></p>
140
+ <p>Modulus (<%= x.key.n.num_bytes * 8 %> Bit):</p>
141
+ <p><code><%= x.n.scan(/.{2}/).join(" ") %></code></p>
142
+ <p>Hash (SHA-256):</p>
143
+ <p>
144
+ <code><%= Base64.decode64(x.public_digest).unpack("H*").join.upcase.scan(/.{2}/).join(" ") %></code>
145
+ </p>
146
+ <p> Fortsetzung auf Seite 2 ...</p>
147
+ <div style="page-break-after:always"></div>
148
+ <h2>EBICS-Initialisierungsbrief (HIA) - Seite 2/2</h2>
149
+ <table>
150
+ <tr>
151
+ <td>
152
+ Datum
153
+ </td>
154
+ <td>
155
+ <%= Date.today.strftime("%d.%m.%Y") %>
156
+ </td>
157
+ </tr>
158
+ <tr>
159
+ <td>
160
+ Uhrzeit
161
+ </td>
162
+ <td>
163
+ <%= Time.now.strftime("%H:%M:%S") %>
164
+ </td>
165
+ </tr>
166
+ <tr>
167
+ <td>
168
+ Empfänger
169
+ </td>
170
+ <td>
171
+ <%= bankname %>
172
+ </td>
173
+ </tr>
174
+ <tr>
175
+ <td>
176
+ User-ID
177
+ </td>
178
+ <td>
179
+ <%= user_id %>
180
+ </td>
181
+ </tr>
182
+ <tr>
183
+ <td>
184
+ Kunden-ID
185
+ </td>
186
+ <td>
187
+ <%= partner_id %>
188
+ </td>
189
+ </tr>
190
+ </table>
191
+ </div>
192
+ <div>
193
+ <p>Öffentlicher Verschlüsselungsschlüssel (E002)</p>
194
+ <p>Exponent (<%= e.key.e.num_bytes * 8 %> Bit):</p>
195
+ <p><code><%= e.e %></code></p>
196
+ <p>Modulus (<%= e.key.n.num_bytes * 8 %> Bit):</p>
197
+ <p><code><%= e.n.scan(/.{2}/).join(" ") %></code></p>
198
+ <p>Hash (SHA-256):</p>
199
+ <p><code><%= Base64.decode64(e.public_digest).unpack("H*").join.upcase.scan(/.{2}/).join(" ") %></code></p>
200
+ <p>Ich bestätige hiermit die obigen öffentlichen Schlüssel für meinen EBICS-Zugang.</p>
201
+ <br/>
202
+ <br/>
203
+ <br/>
204
+ <br/>
205
+ <table>
206
+ <tr>
207
+ <td>
208
+ _________________________
209
+ </td>
210
+ <td>
211
+ _________________________
212
+ </td>
213
+ <td>
214
+ _________________________
215
+ </td>
216
+ </tr>
217
+ <tr>
218
+ <td>
219
+ Ort/Datum
220
+ </td>
221
+ <td>
222
+ Name/Firma
223
+ </td>
224
+ <td>
225
+ Unterschrift
226
+ </td>
227
+ </tr>
228
+ </table>
229
+ </div>
230
+ </body>
231
+ </html>