首先要了解什麼叫對稱加密和非對稱加密,消息摘要這些知識。
對稱加密和非對稱加密可以看文章:對稱加密算法和非對稱加密算法對比_weixin_38081382的部落格-CSDN部落格
1. 非對稱加密
在通信雙方,如果使用非對稱加密,一般遵從這樣的原則:公鑰加密,私鑰解密。同時,一般一個密鑰加密,另一個密鑰就可以解密。
因為公鑰是公開的,如果用來解密,那麼就很容易被不必要的人解密消息。是以,私鑰也可以認為是個人身份的證明。
如果通信雙方需要互發消息,那麼應該建立兩套非對稱加密的機制(即兩對公私鑰密鑰對),發消息的一方使用對方的公鑰進行加密,接收消息的一方使用自己的私鑰解密。
2.消息摘要
消息摘要可以将消息哈希轉換成一個固定長度的值唯一的字元串。值唯一的意思是不同的消息轉換的摘要是不同的,并且能夠確定唯一。該過程不可逆,即不能通過摘要反推明文(似乎SHA1已經可以被破解了,SHA2還沒有。一般認為不可破解,或者破解需要耗費太多時間,成本效益低)。
利用這一特性,可以驗證消息的完整性。
消息摘要通常用在數字簽名中,下面介紹用法。
了解基礎知識之後,就可以看一下數字簽名和數字證書了。
3.數字簽名
假設現在有通信雙方A和B,兩者之間使用兩套非對稱加密機制。
現在A向B發消息。
那麼,如果在發送過程中,有人修改了裡面密文消息,B拿到的密文,解密之後得到明文,并非A所發送的,資訊不正确。
要解決兩個問題:1. A的身份認證 2. A發送的消息完整性 那麼就要用到上面所講的基礎知識。
數字簽名的過程如下圖:
簡單解釋:
A:将明文進行摘要運算後得到摘要(消息完整性),再将摘要用A的私鑰加密(身份認證),得到數字簽名,将密文和數字簽名一塊發給B。
B:收到A的消息後,先将密文用自己的私鑰解密,得到明文。将數字簽名用A的公鑰進行解密後,得到正确的摘要(解密成功說明A的身份被認證了)。
對明文進行摘要運算,得到實際收到的摘要,将兩份摘要進行對比,如果一緻,說明消息沒有被篡改(消息完整性)。
疑問:
摘要使用A的私鑰加密,如果被擁有A的公鑰的第三者截獲,不就可以擷取到摘要了麼?會不會對安全造成威脅。
不會。因為摘要是不可逆推出原文的。
4.數字證書
了解了數字簽名之後,數字證書就好了解了。
由于網絡上通信的雙方可能都不認識對方,那麼就需要第三者來介紹,這就是數字證書。
數字證書由Certificate Authority( CA 認證中心)頒發。
關于數字證書的具體描述,需要百度,目前未完全了解。記一個TODO。
圖解如下:
首先A B雙方要互相信任對方證書。//TODO
然後就可以進行通信了,與上面的數字簽名相似。不同的是,使用了對稱加密。這是因為,非對稱加密在解密過程中,消耗的時間遠遠超過對稱加密。如果密文很長,那麼效率就比較低下了。但密鑰一般不會特别長,對對稱加密的密鑰的加解密可以提高效率。
數字證書簡介及Java編碼實作
1.數字證書簡介
數字證書具備正常加密解密必要的資訊,包含簽名算法,可用于網絡資料加密解密互動,辨別網絡使用者(計算機)身份。數字證書為釋出公鑰提供了一種簡便的途徑,其數字證書則成為加密算法以及公鑰的載體。依靠數字證書,我們可以建構一個簡單的加密網絡應用平台。
數字證書類似于個人身份證,由數字證書頒發認證機構(Certificate Authority, CA)簽發。隻有經過CA簽發的證書在網絡中才具備可認證性。CA頒發給自己的證書叫根證書。
VeriSign, GeoTrust和Thawte是國際權威數字證書頒發認證機構的三巨頭。其中應用最廣泛的是VeriSign簽發的電子商務用數字證書。
最為常用的非對稱加密算法是RSA,與之配套的簽名算法是SHA1withRSA,最常用的消息摘要算法是SHA1.
除了RSA,還可以使用DSA算法。隻是使用DSA算法無法完成加密解密實作,即這樣的證書不包括加密解密功能。
數字證書有多種檔案編碼格式,主要包含CER編碼,DER編碼等。
CER(Canonical Encoding Rules, 規範編碼格式),DER(Distinguished Encoding Rules 卓越編碼格式),兩者的差別是前者是變長模式,後者是定長模式。
所有證書都符合公鑰基礎設施(PKI, Public Key Infrastructure)制定的ITU-T X509國際标準(X.509标準)。
2.模型分析
在實際應用中,很多數字證書都屬于自簽名證書,即證書申請者為自己的證書簽名。這類證書通常應用于軟體廠商内部發放的産品中,或約定使用該證書的資料互動雙方。數字證書完全充當加密算法的載體,為必要資料做加密解密和簽名驗簽等操作。在我司的開發過程中,數字證書更多是用來做加密和解密。
1)證書簽發
2)加密互動,圖略。
當用戶端擷取到伺服器下發的數字證書後,就可以進行加密互動了。具體做法是:
用戶端使用公鑰,加密後發送給服務端,服務端用私鑰進行解密驗證。
服務端使用私鑰進行加密和數字簽名。
3. KeyTool 管理證書
KeyTool與本地密鑰庫相關聯,将私鑰存于密鑰庫,公鑰則以數字證書輸出。KeyTool位于JDK目錄下的bin目錄中,需要通過指令行進行相應的操作。
1)建構自簽名證書
申請數字證書之前,需要在密鑰庫中以别名的方式生成本地數字證書,建立相應的加密算法,密鑰,有效期等資訊。
keytool -genkeypair -keyalg RSA -keysize 2048 -sigalg SHA1withRSA -validity 3600 -alias myCertificate -keystore myKeystore.keystore
各參數含義如下:
-genkeypair 表示生成密鑰對
-keyalg 指定密鑰算法,這裡是RSA
-keysize 指定密鑰長度,預設1024,這裡指定2048
-sigal 指定簽名算法,這裡是SHA1withRSA
-validity 指定有效期,機關為天
-alias 指定别名
-keystore 指定密鑰庫存儲位置
這裡我輸入參數Changeme123作為密鑰庫的密碼,也可通過參數-storepass指定密碼。可以用-dname "CN=xxx...."這樣的形式,避免更多互動。
注意:一個keystore應該是可以存儲多套<私鑰-數字證書>的資訊,通過别名來區分。通過實踐,調用上述指令兩次(别名不同),生成同一個keystore,用不同别名進行加密解密和簽名驗簽,沒有任何問題。
更多指令可參考:http://blog.chinaunix.net/uid-17102734-id-2830223.html
經過上述操作後,密鑰庫中已經建立了數字證書。雖然這時的數字證書并沒有經過CA認證,但并不影響我們使用。我們仍可将證書導出,發送給合作夥伴進行加密互動。
keytool -exportcert -alias myCertificate -keystore myKeystore.keystore -file myCer.cer -rfc
各參數含義如下:
-exportcert 表示證書導出操作
-alias 指定别名
-keystore 指定密鑰庫檔案
-file 指定導出證書的檔案路徑
-rfc 指定以Base64編碼格式輸出
列印證書
keytool -printcert -file myCer.cer
2)建構CA簽發證書
如果要擷取CA機構誰的數字證書,需要将數字證書簽發申請(CSR)導出,經由CA機構認證并頒發,将認證後的證書導入本地密鑰庫和資訊庫。
keytool -certreq -alias myCertificate -keystore myKeystore.keystore -file myCsr.csr -v
各參數含義如下:
-certreq 表示數字證書申請操作
-alias 指定别名
-keystore 指定密鑰庫檔案路徑
-file 指定導出申請的路徑
-v 詳細資訊
獲得簽發的數字證書後,需要将其導入信任庫。
keytool -importcert -trustcacerts -alias myCertificate -file myCer.cer -keystore myKeystore.keystore
參數不作詳細講解,如果是原來的證書檔案,那麼會報錯:
檢視證書
keytool -list -alias myCertificate -keystore myKeystore.keystore
經過上述的所有操作後,可以得到下面幾個檔案
4. 證書使用
終于到了激動人心的時刻,可以用代碼通過keystore進行加解密操作了!
Java 6提供了完善的數字證書管理實作,我們幾乎無需關注,僅通過操作密鑰庫和數字證書就可完成相應的加密解密和簽名驗簽過程。
密鑰庫管理私鑰,數字證書管理公鑰,公鑰和私鑰分屬消息傳遞雙方,進行加密消息傳遞。
考慮一個場景。
A機器某子產品需要将資料導出到一個檔案中,将檔案發送到B機器,由B将資料導入。
在這個場景中,A就相當于服務端,需要将證書給B,同時用私鑰加密資料,生成簽名,導出到檔案中。
B相當于用戶端,用收到的數字證書進行解密和驗簽。、
package jdbc.pro.lin;
import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.InputStream;import java.security.InvalidKeyException;import java.security.KeyStore;import java.security.KeyStoreException;import java.security.NoSuchAlgorithmException;import java.security.PrivateKey;import java.security.PublicKey;import java.security.Signature;import java.security.SignatureException;import java.security.UnrecoverableKeyException;import java.security.cert.Certificate;import java.security.cert.CertificateException;import java.security.cert.CertificateFactory;import java.security.cert.X509Certificate;
import javax.crypto.BadPaddingException;import javax.crypto.Cipher;import javax.crypto.IllegalBlockSizeException;import javax.crypto.NoSuchPaddingException;
public class MyCertifacate { private static final String STORE_PASS = "abcd1234"; private static final String ALIAS = "myCertificate"; private static final String KEYSTORE_PATH = "D:\\Program Files\\Java\\jdk1.8.0_101\\bin\\myKeystore.keystore";//這個私鑰可以放在硬碟的其他地方 private static final String CERT_PATH = "D:\\Program Files\\Java\\jdk1.8.0_101\\bin\\myCer.cer";//這個數字證書也可以放到硬碟的其他地方 private static final String PLAIN_TEXT = "MANUTD is the most greatest club in the world."; /** JDK6隻支援X.509标準的證書 */ private static final String CERT_TYPE = "X.509";
public static void main(String[] args) throws IOException { /** * 假設現在有這樣一個場景 。A機器上的資料,需要加密導出,然後将導出檔案放到B機器上導入。 在這個場景中,A相當于伺服器,B相當于用戶端 */
/** A */ KeyStore keyStore = getKeyStore(STORE_PASS, KEYSTORE_PATH); PrivateKey privateKey = getPrivateKey(keyStore, ALIAS, STORE_PASS); X509Certificate certificate = getCertificateByKeystore(keyStore, ALIAS);
/** 加密和簽名 */ byte[] encodedText = encode(PLAIN_TEXT.getBytes(), privateKey); byte[] signature = sign(certificate, privateKey, PLAIN_TEXT.getBytes());
/** 現在B收到了A的密文和簽名,以及A的可信任證書 */ X509Certificate receivedCertificate = getCertificateByCertPath( CERT_PATH, CERT_TYPE); PublicKey publicKey = getPublicKey(receivedCertificate); byte[] decodedText = decode(encodedText, publicKey); System.out.println("Decoded Text : " + new String(decodedText)); System.out.println("Signature is : " + verify(receivedCertificate, decodedText, signature)); }
/** * 加載密鑰庫,與Properties檔案的加載類似,都是使用load方法 * * @throws IOException */ public static KeyStore getKeyStore(String storepass, String keystorePath) throws IOException { InputStream inputStream = null; try { KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); inputStream = new FileInputStream(keystorePath); keyStore.load(inputStream, storepass.toCharArray()); return keyStore; } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if (null != inputStream) { inputStream.close(); } } return null; }
/** * 擷取私鑰 * * @param keyStore * @param alias * @param password * @return */ public static PrivateKey getPrivateKey(KeyStore keyStore, String alias, String password) { try { return (PrivateKey) keyStore.getKey(alias, password.toCharArray()); } catch (UnrecoverableKeyException | KeyStoreException | NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; }
/** * 擷取公鑰 * * @param certificate * @return */ public static PublicKey getPublicKey(Certificate certificate) { return certificate.getPublicKey(); }
/** * 通過密鑰庫擷取數字證書,不需要密碼,因為擷取到Keystore執行個體 * * @param keyStore * @param alias * @return */ public static X509Certificate getCertificateByKeystore(KeyStore keyStore, String alias) { try { return (X509Certificate) keyStore.getCertificate(alias); } catch (KeyStoreException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; }
/** * 通過證書路徑生成證書,與加載密鑰庫差不多,都要用到流。 * * @param path * @param certType * @return * @throws IOException */ public static X509Certificate getCertificateByCertPath(String path, String certType) throws IOException { InputStream inputStream = null; try { // 執行個體化證書工廠 CertificateFactory factory = CertificateFactory .getInstance(certType); // 取得證書檔案流 inputStream = new FileInputStream(path); // 生成證書 Certificate certificate = factory.generateCertificate(inputStream);
return (X509Certificate) certificate; } catch (CertificateException | IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if (null != inputStream) { inputStream.close(); } } return null;
}
/** * 從證書中擷取加密算法,進行簽名 * * @param certificate * @param privateKey * @param plainText * @return */ public static byte[] sign(X509Certificate certificate, PrivateKey privateKey, byte[] plainText) { /** 如果要從密鑰庫擷取簽名算法的名稱,隻能将其強制轉換成X509标準,JDK 6隻支援X.509類型的證書 */ try { Signature signature = Signature.getInstance(certificate .getSigAlgName()); signature.initSign(privateKey); signature.update(plainText); return signature.sign(); } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { // TODO Auto-generated catch block e.printStackTrace(); }
return null; }
/** * 驗簽,公鑰包含在證書裡面 * * @param certificate * @param decodedText * @param receivedignature * @return */ public static boolean verify(X509Certificate certificate, byte[] decodedText, final byte[] receivedignature) { try { Signature signature = Signature.getInstance(certificate .getSigAlgName()); /** 注意這裡用到的是證書,實際上用到的也是證書裡面的公鑰 */ signature.initVerify(certificate); signature.update(decodedText); return signature.verify(receivedignature); } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { // TODO Auto-generated catch block e.printStackTrace(); } return false; }
/** * 加密。注意密鑰是可以擷取到它适用的算法的。 * * @param plainText * @param privateKey * @return */ public static byte[] encode(byte[] plainText, PrivateKey privateKey) { try { Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm()); cipher.init(Cipher.ENCRYPT_MODE, privateKey); return cipher.doFinal(plainText); } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { // TODO Auto-generated catch block e.printStackTrace(); }
return null;
}
/** * 解密,注意密鑰是可以擷取它适用的算法的。 * * @param encodedText * @param publicKey * @return */ public static byte[] decode(byte[] encodedText, PublicKey publicKey) { try { Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm()); cipher.init(Cipher.DECRYPT_MODE, publicKey); return cipher.doFinal(encodedText); } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { // TODO Auto-generated catch block e.printStackTrace(); }
return null; }}
1 package jdbc.pro.lin;
2
3 import java.io.FileInputStream;
4 import java.io.FileNotFoundException;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.security.InvalidKeyException;
8 import java.security.KeyStore;
9 import java.security.KeyStoreException;
10 import java.security.NoSuchAlgorithmException;
11 import java.security.PrivateKey;
12 import java.security.PublicKey;
13 import java.security.Signature;
14 import java.security.SignatureException;
15 import java.security.UnrecoverableKeyException;
16 import java.security.cert.Certificate;
17 import java.security.cert.CertificateException;
18 import java.security.cert.CertificateFactory;
19 import java.security.cert.X509Certificate;
20
21 import javax.crypto.BadPaddingException;
22 import javax.crypto.Cipher;
23 import javax.crypto.IllegalBlockSizeException;
24 import javax.crypto.NoSuchPaddingException;
25
26 public class MyCertifacate {
27 private static final String STORE_PASS = "Changeme123";
28 private static final String ALIAS = "myCertificate";
29 private static final String KEYSTORE_PATH = "D:\\JavaDemo\\Certifacate\\myKeystore.keystore";
30 private static final String CERT_PATH = "D:\\JavaDemo\\Certifacate\\myCer.cer";
31 private static final String PLAIN_TEXT = "MANUTD is the most greatest club in the world.";
32 /** JDK6隻支援X.509标準的證書 */
33 private static final String CERT_TYPE = "X.509";
34
35 public static void main(String[] args) throws IOException {
36 /**
37 * 假設現在有這樣一個場景 。A機器上的資料,需要加密導出,然後将導出檔案放到B機器上導入。 在這個場景中,A相當于伺服器,B相當于用戶端
38 */
39
40 /** A */
41 KeyStore keyStore = getKeyStore(STORE_PASS, KEYSTORE_PATH);
42 PrivateKey privateKey = getPrivateKey(keyStore, ALIAS, STORE_PASS);
43 X509Certificate certificate = getCertificateByKeystore(keyStore, ALIAS);
44
45 /** 加密和簽名 */
46 byte[] encodedText = encode(PLAIN_TEXT.getBytes(), privateKey);
47 byte[] signature = sign(certificate, privateKey, PLAIN_TEXT.getBytes());
48
49 /** 現在B收到了A的密文和簽名,以及A的可信任證書 */
50 X509Certificate receivedCertificate = getCertificateByCertPath(
51 CERT_PATH, CERT_TYPE);
52 PublicKey publicKey = getPublicKey(receivedCertificate);
53 byte[] decodedText = decode(encodedText, publicKey);
54 System.out.println("Decoded Text : " + new String(decodedText));
55 System.out.println("Signature is : "
56 + verify(receivedCertificate, decodedText, signature));
57 }
58
59 /**
60 * 加載密鑰庫,與Properties檔案的加載類似,都是使用load方法
61 *
62 * @throws IOException
63 */
64 public static KeyStore getKeyStore(String storepass, String keystorePath)
65 throws IOException {
66 InputStream inputStream = null;
67 try {
68 KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
69 inputStream = new FileInputStream(keystorePath);
70 keyStore.load(inputStream, storepass.toCharArray());
71 return keyStore;
72 } catch (KeyStoreException | NoSuchAlgorithmException
73 | CertificateException | IOException e) {
74 // TODO Auto-generated catch block
75 e.printStackTrace();
76 } finally {
77 if (null != inputStream) {
78 inputStream.close();
79 }
80 }
81 return null;
82 }
83
84 /**
85 * 擷取私鑰
86 *
87 * @param keyStore
88 * @param alias
89 * @param password
90 * @return
91 */
92 public static PrivateKey getPrivateKey(KeyStore keyStore, String alias,
93 String password) {
94 try {
95 return (PrivateKey) keyStore.getKey(alias, password.toCharArray());
96 } catch (UnrecoverableKeyException | KeyStoreException
97 | NoSuchAlgorithmException e) {
98 // TODO Auto-generated catch block
99 e.printStackTrace();
100 }
101 return null;
102 }
103
104 /**
105 * 擷取公鑰
106 *
107 * @param certificate
108 * @return
109 */
110 public static PublicKey getPublicKey(Certificate certificate) {
111 return certificate.getPublicKey();
112 }
113
114 /**
115 * 通過密鑰庫擷取數字證書,不需要密碼,因為擷取到Keystore執行個體
116 *
117 * @param keyStore
118 * @param alias
119 * @return
120 */
121 public static X509Certificate getCertificateByKeystore(KeyStore keyStore,
122 String alias) {
123 try {
124 return (X509Certificate) keyStore.getCertificate(alias);
125 } catch (KeyStoreException e) {
126 // TODO Auto-generated catch block
127 e.printStackTrace();
128 }
129 return null;
130 }
131
132 /**
133 * 通過證書路徑生成證書,與加載密鑰庫差不多,都要用到流。
134 *
135 * @param path
136 * @param certType
137 * @return
138 * @throws IOException
139 */
140 public static X509Certificate getCertificateByCertPath(String path,
141 String certType) throws IOException {
142 InputStream inputStream = null;
143 try {
144 // 執行個體化證書工廠
145 CertificateFactory factory = CertificateFactory
146 .getInstance(certType);
147 // 取得證書檔案流
148 inputStream = new FileInputStream(path);
149 // 生成證書
150 Certificate certificate = factory.generateCertificate(inputStream);
151
152 return (X509Certificate) certificate;
153 } catch (CertificateException | IOException e) {
154 // TODO Auto-generated catch block
155 e.printStackTrace();
156 } finally {
157 if (null != inputStream) {
158 inputStream.close();
159 }
160 }
161 return null;
162
163 }
164
165 /**
166 * 從證書中擷取加密算法,進行簽名
167 *
168 * @param certificate
169 * @param privateKey
170 * @param plainText
171 * @return
172 */
173 public static byte[] sign(X509Certificate certificate,
174 PrivateKey privateKey, byte[] plainText) {
175 /** 如果要從密鑰庫擷取簽名算法的名稱,隻能将其強制轉換成X509标準,JDK 6隻支援X.509類型的證書 */
176 try {
177 Signature signature = Signature.getInstance(certificate
178 .getSigAlgName());
179 signature.initSign(privateKey);
180 signature.update(plainText);
181 return signature.sign();
182 } catch (NoSuchAlgorithmException | InvalidKeyException
183 | SignatureException e) {
184 // TODO Auto-generated catch block
185 e.printStackTrace();
186 }
187
188 return null;
189 }
190
191 /**
192 * 驗簽,公鑰包含在證書裡面
193 *
194 * @param certificate
195 * @param decodedText
196 * @param receivedignature
197 * @return
198 */
199 public static boolean verify(X509Certificate certificate,
200 byte[] decodedText, final byte[] receivedignature) {
201 try {
202 Signature signature = Signature.getInstance(certificate
203 .getSigAlgName());
204 /** 注意這裡用到的是證書,實際上用到的也是證書裡面的公鑰 */
205 signature.initVerify(certificate);
206 signature.update(decodedText);
207 return signature.verify(receivedignature);
208 } catch (NoSuchAlgorithmException | InvalidKeyException
209 | SignatureException e) {
210 // TODO Auto-generated catch block
211 e.printStackTrace();
212 }
213 return false;
214 }
215
216 /**
217 * 加密。注意密鑰是可以擷取到它适用的算法的。
218 *
219 * @param plainText
220 * @param privateKey
221 * @return
222 */
223 public static byte[] encode(byte[] plainText, PrivateKey privateKey) {
224 try {
225 Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
226 cipher.init(Cipher.ENCRYPT_MODE, privateKey);
227 return cipher.doFinal(plainText);
228 } catch (NoSuchAlgorithmException | NoSuchPaddingException
229 | InvalidKeyException | IllegalBlockSizeException
230 | BadPaddingException e) {
231 // TODO Auto-generated catch block
232 e.printStackTrace();
233 }
234
235 return null;
236
237 }
238
239 /**
240 * 解密,注意密鑰是可以擷取它适用的算法的。
241 *
242 * @param encodedText
243 * @param publicKey
244 * @return
245 */
246 public static byte[] decode(byte[] encodedText, PublicKey publicKey) {
247 try {
248 Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
249 cipher.init(Cipher.DECRYPT_MODE, publicKey);
250 return cipher.doFinal(encodedText);
251 } catch (NoSuchAlgorithmException | NoSuchPaddingException
252 | InvalidKeyException | IllegalBlockSizeException
253 | BadPaddingException e) {
254 // TODO Auto-generated catch block
255 e.printStackTrace();
256 }
257
258 return null;
259 }
260 }