epics 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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>