目標
程式中可選擇使用「舊」或「新」加解密。
application.yml 等組態同時支援兩種加密字串(便於平滑換代)。
A. 程式碼中手動加解密:用 @Qualifier 注入兩個 Encryptor
定義兩個 StringEncryptor Bean:
legacyEncryptor:對應你原本的 PBEWithMD5AndDES + NoIv + 固定密碼。
encryptorBean:協力商那組 PBEWithHmacSHA512AndAES_256 + RandomIv + 動態 salt。
@Configuration
public class JasyptEncryptorsConfig {
// 舊:僅為相容用(弱加密,無 IV)
@Bean("legacyEncryptor")
public StringEncryptor legacyEncryptor(
@Value("${jasypt.encryptor.password}") String legacyPassword) {
PooledPBEStringEncryptor enc = new PooledPBEStringEncryptor();
SimpleStringPBEConfig cfg = new SimpleStringPBEConfig();
cfg.setPassword(legacyPassword);
cfg.setAlgorithm("PBEWithMD5AndDES");
cfg.setIvGeneratorClassName("org.jasypt.iv.NoIvGenerator");
cfg.setPoolSize("1");
cfg.setStringOutputType("base64");
enc.setConfig(cfg);
return enc;
}
// 新:協力商版(安全組態)
@Bean("encryptorBean")
public StringEncryptor encryptorBean() {
String p1 = System.getProperty("P_ENV_1");
String p2 = System.getProperty("P_ENV_2");
if (org.apache.commons.lang3.StringUtils.isBlank(p1)
|| org.apache.commons.lang3.StringUtils.isBlank(p2)
|| "null".equalsIgnoreCase(p1)
|| "null".equalsIgnoreCase(p2)) {
p1 = System.getenv("P_ENV_1");
p2 = System.getenv("P_ENV_2");
}
String salt = (p1 == null ? "" : p1) + (p2 == null ? "" : p2);
PooledPBEStringEncryptor enc = new PooledPBEStringEncryptor();
SimpleStringPBEConfig cfg = new SimpleStringPBEConfig();
cfg.setPassword(salt);
cfg.setAlgorithm("PBEWithHmacSHA512AndAES_256");
cfg.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
cfg.setKeyObtentionIterations("1000");
cfg.setPoolSize("1");
cfg.setProviderName("SunJCE");
cfg.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
cfg.setStringOutputType("base64");
enc.setConfig(cfg);
return enc;
}
}
使用時用 @Qualifier 指定:
@Service
public class CryptoService {
private final StringEncryptor legacy;
private final StringEncryptor modern;
public CryptoService(@Qualifier("legacyEncryptor") StringEncryptor legacy,
@Qualifier("encryptorBean") StringEncryptor modern) {
this.legacy = legacy;
this.modern = modern;
}
public String encryptNew(String plain) { return modern.encrypt(plain); }
public String decryptNew(String cipher) { return modern.decrypt(cipher); }
public String decryptLegacy(String cipher) { return legacy.decrypt(cipher); }
// 如果需要:自動判斷哪種格式(簡單範例)
public String smartDecrypt(String cipher) {
try { return modern.decrypt(cipher); }
catch (Exception ignore) { /* fall through */ }
return legacy.decrypt(cipher);
}
}
B. 組態屬性的自動解密:用「路由型」Encryptor + 多前綴
ulisesbocchio/jasypt-spring-boot 預設一次只能指定一個 jasypt.encryptor.bean 來處理組態的 ENC(...)。若你要同時支援「舊」與「新」兩種加密格式,有兩條路:
路線 1(推薦):自訂前綴切換
定義不同前綴,例如:
新格式:ENC_NEW(...) → 用 encryptorBean
舊格式:ENC_OLD(...) → 用 legacyEncryptor
做法:提供自訂 Detector/Resolver 或一個路由型 Encryptor 配合自訂 Detector,讓它看前綴決定用哪個 encryptor。
@Configuration
public class JasyptRoutingConfig {
@Bean("routingEncryptor")
public StringEncryptor routingEncryptor(
@Qualifier("legacyEncryptor") StringEncryptor legacy,
@Qualifier("encryptorBean") StringEncryptor modern) {
return new StringEncryptor() {
@Override
public String encrypt(String message) {
// 產生新資料一律用新演算法
return modern.encrypt(message);
}
@Override
public String decrypt(String encryptedMessage) {
// 這裡單純嘗試兩次;也可結合前綴判斷
try { return modern.decrypt(encryptedMessage); }
catch (Exception ignore) { /* fall through */ }
return legacy.decrypt(encryptedMessage);
}
};
}
// 自訂前綴偵測:支援 ENC_NEW(...)、ENC_OLD(...)、也保留 ENC(...)
@Bean
public com.ulisesbocchio.jasyptspringboot.detector.EncryptablePropertyDetector encryptablePropertyDetector() {
return value -> {
if (value == null) return false;
return value.startsWith("ENC_NEW(") || value.startsWith("ENC_OLD(") || value.startsWith("ENC(");
};
}
@Bean
public com.ulisesbocchio.jasyptspringboot.resolver.EncryptablePropertyResolver encryptablePropertyResolver(
@Qualifier("legacyEncryptor") StringEncryptor legacy,
@Qualifier("encryptorBean") StringEncryptor modern) {
return value -> {
if (value == null) return null;
if (value.startsWith("ENC_NEW(") && value.endsWith(")")) {
String body = value.substring("ENC_NEW(".length(), value.length() - 1);
return modern.decrypt(body);
}
if (value.startsWith("ENC_OLD(") && value.endsWith(")")) {
String body = value.substring("ENC_OLD(".length(), value.length() - 1);
return legacy.decrypt(body);
}
if (value.startsWith("ENC(") && value.endsWith(")")) {
String body = value.substring("ENC(".length(), value.length() - 1);
// 兼容老專案:先試新,不行再回退舊
try { return modern.decrypt(body); }
catch (Exception ignore) { return legacy.decrypt(body); }
}
return value;
};
}
}
application.yml 指向這個「路由」能力(其實 Resolver 已經接手了,這行可以保留指向 routingEncryptor;或直接不設):
jasypt:
encryptor:
bean: routingEncryptor
然後在組態檔裡你可以混用:
# 新格式(AES-256)
spring:
datasource:
password: "ENC_NEW(xxxxxxx...)"
# 舊格式(MD5+DES)
legacy:
token: "ENC_OLD(yyyyyyy...)"
# 未標記舊新者(歷史遺留)
other:
secret: "ENC(zzzzzz...)"
路線 2:只掛一個 jasypt.encryptor.bean
如果只在組態層面啟用新演算法(jasypt.encryptor.bean=encryptorBean),舊資料不再用於組態,就不需要路由。舊資料若還會在程式中用到,就改由程式碼的 @Qualifier("legacyEncryptor") 來解即可。這是最簡潔,但無法同時在組態屬性裡解兩種格式。
C. 產出與轉換策略(實務建議)
新資料一律用 AES-256(encryptorBean)產生;避免再寫入 MD5+DES。
舊資料保留解密能力一段時間(legacyEncryptor),逐步轉檔或於讀取時即時轉存為新格式。
把組態值改標記為 ENC_NEW(...),僅在極端相容需求時才保留 ENC_OLD(...)。
D. 典型落坑提醒
你的原始片段有些小誤植:config. setPassword(sait)(應為 salt)、config-setAlgorithm(應為 setAlgorithm)、全形括號與空白等;上面提供的程式碼已修正。
PBEWithMD5AndDES + NoIvGenerator 安全性很弱,只作相容期使用,並避免再用於新密文。
P_ENV_1/P_ENV_2 來自 JVM 系統屬性或環境變數時,要考慮空/null,已在範例處理。
JCE AES-256 在舊 JDK 曾需「無限制強度策略檔」;現代 JDK 已內建,仍建議在所有環境驗證一次。
E. 最小可用配置總結
@Bean("legacyEncryptor") + @Bean("encryptorBean") 兩組都註冊。
組態層若需同時支援兩種格式:加上自訂 Detector/Resolver(或路由 Encryptor),並允許 ENC_NEW(...)、ENC_OLD(...)。
程式碼層用 @Qualifier 精準選擇要用哪一組。
接下來若你要,我可以產生一支小測試(JUnit 或簡單 main())幫你把明文轉成 ENC_NEW(...) / ENC_OLD(...) 兩種密文,方便你替換組態。
55uu5qiZCgoKCueoi+W8j+S4reWPr+mBuOaTh+S9v+eUqOOAjOiIiuOAjeaIluOAjOaWsOOAjeWKoOino+WvhuOAggoKCgphcHBsaWNhdGlvbi55bWwg562J57WE5oWL5ZCM5pmC5pSv5o+05YWp56iu5Yqg5a+G5a2X5Liy77yI5L6/5pa85bmz5ruR5o+b5Luj77yJ44CCCgoKCkEuIOeoi+W8j+eivOS4reaJi+WLleWKoOino+Wvhu+8mueUqCBAUXVhbGlmaWVyIOazqOWFpeWFqeWAiyBFbmNyeXB0b3IKCgoK5a6a576p5YWp5YCLIFN0cmluZ0VuY3J5cHRvciBCZWFu77yaCgoKCmxlZ2FjeUVuY3J5cHRvcu+8muWwjeaHieS9oOWOn+acrOeahCBQQkVXaXRoTUQ1QW5kREVTICsgTm9JdiArIOWbuuWumuWvhueivOOAggoKCgplbmNyeXB0b3JCZWFu77ya5Y2U5Yqb5ZWG6YKj57WEIFBCRVdpdGhIbWFjU0hBNTEyQW5kQUVTXzI1NiArIFJhbmRvbUl2ICsg5YuV5oWLIHNhbHTjgIIKCgoKQENvbmZpZ3VyYXRpb24KCnB1YmxpYyBjbGFzcyBKYXN5cHRFbmNyeXB0b3JzQ29uZmlnIHsKCgoKICAgIC8vIOiIiu+8muWDheeCuuebuOWuueeUqO+8iOW8seWKoOWvhu+8jOeEoSBJVu+8iQoKICAgIEBCZWFuKCJsZWdhY3lFbmNyeXB0b3IiKQoKICAgIHB1YmxpYyBTdHJpbmdFbmNyeXB0b3IgbGVnYWN5RW5jcnlwdG9yKAoKICAgICAgICAgICAgQFZhbHVlKCIke2phc3lwdC5lbmNyeXB0b3IucGFzc3dvcmR9IikgU3RyaW5nIGxlZ2FjeVBhc3N3b3JkKSB7CgogICAgICAgIFBvb2xlZFBCRVN0cmluZ0VuY3J5cHRvciBlbmMgPSBuZXcgUG9vbGVkUEJFU3RyaW5nRW5jcnlwdG9yKCk7CgogICAgICAgIFNpbXBsZVN0cmluZ1BCRUNvbmZpZyBjZmcgPSBuZXcgU2ltcGxlU3RyaW5nUEJFQ29uZmlnKCk7CgogICAgICAgIGNmZy5zZXRQYXNzd29yZChsZWdhY3lQYXNzd29yZCk7CgogICAgICAgIGNmZy5zZXRBbGdvcml0aG0oIlBCRVdpdGhNRDVBbmRERVMiKTsKCiAgICAgICAgY2ZnLnNldEl2R2VuZXJhdG9yQ2xhc3NOYW1lKCJvcmcuamFzeXB0Lml2Lk5vSXZHZW5lcmF0b3IiKTsKCiAgICAgICAgY2ZnLnNldFBvb2xTaXplKCIxIik7CgogICAgICAgIGNmZy5zZXRTdHJpbmdPdXRwdXRUeXBlKCJiYXNlNjQiKTsKCiAgICAgICAgZW5jLnNldENvbmZpZyhjZmcpOwoKICAgICAgICByZXR1cm4gZW5jOwoKICAgIH0KCgoKICAgIC8vIOaWsO+8muWNlOWKm+WVhueJiO+8iOWuieWFqOe1hOaFi++8iQoKICAgIEBCZWFuKCJlbmNyeXB0b3JCZWFuIikKCiAgICBwdWJsaWMgU3RyaW5nRW5jcnlwdG9yIGVuY3J5cHRvckJlYW4oKSB7CgogICAgICAgIFN0cmluZyBwMSA9IFN5c3RlbS5nZXRQcm9wZXJ0eSgiUF9FTlZfMSIpOwoKICAgICAgICBTdHJpbmcgcDIgPSBTeXN0ZW0uZ2V0UHJvcGVydHkoIlBfRU5WXzIiKTsKCiAgICAgICAgaWYgKG9yZy5hcGFjaGUuY29tbW9ucy5sYW5nMy5TdHJpbmdVdGlscy5pc0JsYW5rKHAxKQoKICAgICAgICAgICAgICAgIHx8IG9yZy5hcGFjaGUuY29tbW9ucy5sYW5nMy5TdHJpbmdVdGlscy5pc0JsYW5rKHAyKQoKICAgICAgICAgICAgICAgIHx8ICJudWxsIi5lcXVhbHNJZ25vcmVDYXNlKHAxKQoKICAgICAgICAgICAgICAgIHx8ICJudWxsIi5lcXVhbHNJZ25vcmVDYXNlKHAyKSkgewoKICAgICAgICAgICAgcDEgPSBTeXN0ZW0uZ2V0ZW52KCJQX0VOVl8xIik7CgogICAgICAgICAgICBwMiA9IFN5c3RlbS5nZXRlbnYoIlBfRU5WXzIiKTsKCiAgICAgICAgfQoKICAgICAgICBTdHJpbmcgc2FsdCA9IChwMSA9PSBudWxsID8gIiIgOiBwMSkgKyAocDIgPT0gbnVsbCA/ICIiIDogcDIpOwoKCgogICAgICAgIFBvb2xlZFBCRVN0cmluZ0VuY3J5cHRvciBlbmMgPSBuZXcgUG9vbGVkUEJFU3RyaW5nRW5jcnlwdG9yKCk7CgogICAgICAgIFNpbXBsZVN0cmluZ1BCRUNvbmZpZyBjZmcgPSBuZXcgU2ltcGxlU3RyaW5nUEJFQ29uZmlnKCk7CgogICAgICAgIGNmZy5zZXRQYXNzd29yZChzYWx0KTsKCiAgICAgICAgY2ZnLnNldEFsZ29yaXRobSgiUEJFV2l0aEhtYWNTSEE1MTJBbmRBRVNfMjU2Iik7CgogICAgICAgIGNmZy5zZXRJdkdlbmVyYXRvckNsYXNzTmFtZSgib3JnLmphc3lwdC5pdi5SYW5kb21JdkdlbmVyYXRvciIpOwoKICAgICAgICBjZmcuc2V0S2V5T2J0ZW50aW9uSXRlcmF0aW9ucygiMTAwMCIpOwoKICAgICAgICBjZmcuc2V0UG9vbFNpemUoIjEiKTsKCiAgICAgICAgY2ZnLnNldFByb3ZpZGVyTmFtZSgiU3VuSkNFIik7CgogICAgICAgIGNmZy5zZXRTYWx0R2VuZXJhdG9yQ2xhc3NOYW1lKCJvcmcuamFzeXB0LnNhbHQuUmFuZG9tU2FsdEdlbmVyYXRvciIpOwoKICAgICAgICBjZmcuc2V0U3RyaW5nT3V0cHV0VHlwZSgiYmFzZTY0Iik7CgogICAgICAgIGVuYy5zZXRDb25maWcoY2ZnKTsKCiAgICAgICAgcmV0dXJuIGVuYzsKCiAgICB9Cgp9CgoKCgoK5L2/55So5pmC55SoIEBRdWFsaWZpZXIg5oyH5a6a77yaCgoKCkBTZXJ2aWNlCgpwdWJsaWMgY2xhc3MgQ3J5cHRvU2VydmljZSB7CgoKCiAgICBwcml2YXRlIGZpbmFsIFN0cmluZ0VuY3J5cHRvciBsZWdhY3k7CgogICAgcHJpdmF0ZSBmaW5hbCBTdHJpbmdFbmNyeXB0b3IgbW9kZXJuOwoKCgogICAgcHVibGljIENyeXB0b1NlcnZpY2UoQFF1YWxpZmllcigibGVnYWN5RW5jcnlwdG9yIikgU3RyaW5nRW5jcnlwdG9yIGxlZ2FjeSwKCiAgICAgICAgICAgICAgICAgICAgICAgICBAUXVhbGlmaWVyKCJlbmNyeXB0b3JCZWFuIikgU3RyaW5nRW5jcnlwdG9yIG1vZGVybikgewoKICAgICAgICB0aGlzLmxlZ2FjeSA9IGxlZ2FjeTsKCiAgICAgICAgdGhpcy5tb2Rlcm4gPSBtb2Rlcm47CgogICAgfQoKCgogICAgcHVibGljIFN0cmluZyBlbmNyeXB0TmV3KFN0cmluZyBwbGFpbikgeyByZXR1cm4gbW9kZXJuLmVuY3J5cHQocGxhaW4pOyB9CgogICAgcHVibGljIFN0cmluZyBkZWNyeXB0TmV3KFN0cmluZyBjaXBoZXIpIHsgcmV0dXJuIG1vZGVybi5kZWNyeXB0KGNpcGhlcik7IH0KCgoKICAgIHB1YmxpYyBTdHJpbmcgZGVjcnlwdExlZ2FjeShTdHJpbmcgY2lwaGVyKSB7IHJldHVybiBsZWdhY3kuZGVjcnlwdChjaXBoZXIpOyB9CgoKCiAgICAvLyDlpoLmnpzpnIDopoHvvJroh6rli5XliKTmlrflk6rnqK7moLzlvI/vvIjnsKHllq7nr4TkvovvvIkKCiAgICBwdWJsaWMgU3RyaW5nIHNtYXJ0RGVjcnlwdChTdHJpbmcgY2lwaGVyKSB7CgogICAgICAgIHRyeSB7IHJldHVybiBtb2Rlcm4uZGVjcnlwdChjaXBoZXIpOyB9CgogICAgICAgIGNhdGNoIChFeGNlcHRpb24gaWdub3JlKSB7IC8qIGZhbGwgdGhyb3VnaCAqLyB9CgogICAgICAgIHJldHVybiBsZWdhY3kuZGVjcnlwdChjaXBoZXIpOwoKICAgIH0KCn0KCgoKQi4g57WE5oWL5bGs5oCn55qE6Ieq5YuV6Kej5a+G77ya55So44CM6Lev55Sx5Z6L44CNRW5jcnlwdG9yICsg5aSa5YmN57a0CgoKCnVsaXNlc2JvY2NoaW8vamFzeXB0LXNwcmluZy1ib290IOmgkOioreS4gOasoeWPquiDveaMh+WumuS4gOWAiyBqYXN5cHQuZW5jcnlwdG9yLmJlYW4g5L6G6JmV55CG57WE5oWL55qEIEVOQyguLi4p44CC6Iul5L2g6KaB5ZCM5pmC5pSv5o+044CM6IiK44CN6IiH44CM5paw44CN5YWp56iu5Yqg5a+G5qC85byP77yM5pyJ5YWp5qKd6Lev77yaCgoKCui3r+e3miAx77yI5o6o6Jam77yJ77ya6Ieq6KiC5YmN57a05YiH5o+bCgoKCuWumue+qeS4jeWQjOWJjee2tO+8jOS+i+Wmgu+8mgoKCgrmlrDmoLzlvI/vvJpFTkNfTkVXKC4uLikg4oaSIOeUqCBlbmNyeXB0b3JCZWFuCgoKCuiIiuagvOW8j++8mkVOQ19PTEQoLi4uKSDihpIg55SoIGxlZ2FjeUVuY3J5cHRvcgoKCgrlgZrms5XvvJrmj5Dkvpvoh6roqIIgRGV0ZWN0b3IvUmVzb2x2ZXIg5oiW5LiA5YCL6Lev55Sx5Z6LIEVuY3J5cHRvciDphY3lkIjoh6roqIIgRGV0ZWN0b3LvvIzorpPlroPnnIvliY3ntrTmsbrlrprnlKjlk6rlgIsgZW5jcnlwdG9y44CCCgoKCkBDb25maWd1cmF0aW9uCgpwdWJsaWMgY2xhc3MgSmFzeXB0Um91dGluZ0NvbmZpZyB7CgoKCiAgICBAQmVhbigicm91dGluZ0VuY3J5cHRvciIpCgogICAgcHVibGljIFN0cmluZ0VuY3J5cHRvciByb3V0aW5nRW5jcnlwdG9yKAoKICAgICAgICAgICAgQFF1YWxpZmllcigibGVnYWN5RW5jcnlwdG9yIikgU3RyaW5nRW5jcnlwdG9yIGxlZ2FjeSwKCiAgICAgICAgICAgIEBRdWFsaWZpZXIoImVuY3J5cHRvckJlYW4iKSBTdHJpbmdFbmNyeXB0b3IgbW9kZXJuKSB7CgogICAgICAgIHJldHVybiBuZXcgU3RyaW5nRW5jcnlwdG9yKCkgewoKICAgICAgICAgICAgQE92ZXJyaWRlCgogICAgICAgICAgICBwdWJsaWMgU3RyaW5nIGVuY3J5cHQoU3RyaW5nIG1lc3NhZ2UpIHsKCiAgICAgICAgICAgICAgICAvLyDnlKLnlJ/mlrDos4fmlpnkuIDlvovnlKjmlrDmvJTnrpfms5UKCiAgICAgICAgICAgICAgICByZXR1cm4gbW9kZXJuLmVuY3J5cHQobWVzc2FnZSk7CgogICAgICAgICAgICB9CgogICAgICAgICAgICBAT3ZlcnJpZGUKCiAgICAgICAgICAgIHB1YmxpYyBTdHJpbmcgZGVjcnlwdChTdHJpbmcgZW5jcnlwdGVkTWVzc2FnZSkgewoKICAgICAgICAgICAgICAgIC8vIOmAmeijoeWWrue0lOWYl+ippuWFqeasoe+8m+S5n+WPr+e1kOWQiOWJjee2tOWIpOaWtwoKICAgICAgICAgICAgICAgIHRyeSB7IHJldHVybiBtb2Rlcm4uZGVjcnlwdChlbmNyeXB0ZWRNZXNzYWdlKTsgfQoKICAgICAgICAgICAgICAgIGNhdGNoIChFeGNlcHRpb24gaWdub3JlKSB7IC8qIGZhbGwgdGhyb3VnaCAqLyB9CgogICAgICAgICAgICAgICAgcmV0dXJuIGxlZ2FjeS5kZWNyeXB0KGVuY3J5cHRlZE1lc3NhZ2UpOwoKICAgICAgICAgICAgfQoKICAgICAgICB9OwoKICAgIH0KCgoKICAgIC8vIOiHquioguWJjee2tOWBtea4rO+8muaUr+aPtCBFTkNfTkVXKC4uLinjgIFFTkNfT0xEKC4uLinjgIHkuZ/kv53nlZkgRU5DKC4uLikKCiAgICBAQmVhbgoKICAgIHB1YmxpYyBjb20udWxpc2VzYm9jY2hpby5qYXN5cHRzcHJpbmdib290LmRldGVjdG9yLkVuY3J5cHRhYmxlUHJvcGVydHlEZXRlY3RvciBlbmNyeXB0YWJsZVByb3BlcnR5RGV0ZWN0b3IoKSB7CgogICAgICAgIHJldHVybiB2YWx1ZSAtPiB7CgogICAgICAgICAgICBpZiAodmFsdWUgPT0gbnVsbCkgcmV0dXJuIGZhbHNlOwoKICAgICAgICAgICAgcmV0dXJuIHZhbHVlLnN0YXJ0c1dpdGgoIkVOQ19ORVcoIikgfHwgdmFsdWUuc3RhcnRzV2l0aCgiRU5DX09MRCgiKSB8fCB2YWx1ZS5zdGFydHNXaXRoKCJFTkMoIik7CgogICAgICAgIH07CgogICAgfQoKCgogICAgQEJlYW4KCiAgICBwdWJsaWMgY29tLnVsaXNlc2JvY2NoaW8uamFzeXB0c3ByaW5nYm9vdC5yZXNvbHZlci5FbmNyeXB0YWJsZVByb3BlcnR5UmVzb2x2ZXIgZW5jcnlwdGFibGVQcm9wZXJ0eVJlc29sdmVyKAoKICAgICAgICAgICAgQFF1YWxpZmllcigibGVnYWN5RW5jcnlwdG9yIikgU3RyaW5nRW5jcnlwdG9yIGxlZ2FjeSwKCiAgICAgICAgICAgIEBRdWFsaWZpZXIoImVuY3J5cHRvckJlYW4iKSBTdHJpbmdFbmNyeXB0b3IgbW9kZXJuKSB7CgoKCiAgICAgICAgcmV0dXJuIHZhbHVlIC0+IHsKCiAgICAgICAgICAgIGlmICh2YWx1ZSA9PSBudWxsKSByZXR1cm4gbnVsbDsKCgoKICAgICAgICAgICAgaWYgKHZhbHVlLnN0YXJ0c1dpdGgoIkVOQ19ORVcoIikgJiYgdmFsdWUuZW5kc1dpdGgoIikiKSkgewoKICAgICAgICAgICAgICAgIFN0cmluZyBib2R5ID0gdmFsdWUuc3Vic3RyaW5nKCJFTkNfTkVXKCIubGVuZ3RoKCksIHZhbHVlLmxlbmd0aCgpIC0gMSk7CgogICAgICAgICAgICAgICAgcmV0dXJuIG1vZGVybi5kZWNyeXB0KGJvZHkpOwoKICAgICAgICAgICAgfQoKICAgICAgICAgICAgaWYgKHZhbHVlLnN0YXJ0c1dpdGgoIkVOQ19PTEQoIikgJiYgdmFsdWUuZW5kc1dpdGgoIikiKSkgewoKICAgICAgICAgICAgICAgIFN0cmluZyBib2R5ID0gdmFsdWUuc3Vic3RyaW5nKCJFTkNfT0xEKCIubGVuZ3RoKCksIHZhbHVlLmxlbmd0aCgpIC0gMSk7CgogICAgICAgICAgICAgICAgcmV0dXJuIGxlZ2FjeS5kZWNyeXB0KGJvZHkpOwoKICAgICAgICAgICAgfQoKICAgICAgICAgICAgaWYgKHZhbHVlLnN0YXJ0c1dpdGgoIkVOQygiKSAmJiB2YWx1ZS5lbmRzV2l0aCgiKSIpKSB7CgogICAgICAgICAgICAgICAgU3RyaW5nIGJvZHkgPSB2YWx1ZS5zdWJzdHJpbmcoIkVOQygiLmxlbmd0aCgpLCB2YWx1ZS5sZW5ndGgoKSAtIDEpOwoKICAgICAgICAgICAgICAgIC8vIOWFvOWuueiAgeWwiOahiO+8muWFiOippuaWsO+8jOS4jeihjOWGjeWbnumAgOiIigoKICAgICAgICAgICAgICAgIHRyeSB7IHJldHVybiBtb2Rlcm4uZGVjcnlwdChib2R5KTsgfQoKICAgICAgICAgICAgICAgIGNhdGNoIChFeGNlcHRpb24gaWdub3JlKSB7IHJldHVybiBsZWdhY3kuZGVjcnlwdChib2R5KTsgfQoKICAgICAgICAgICAgfQoKICAgICAgICAgICAgcmV0dXJuIHZhbHVlOwoKICAgICAgICB9OwoKICAgIH0KCn0KCgoKCgphcHBsaWNhdGlvbi55bWwg5oyH5ZCR6YCZ5YCL44CM6Lev55Sx44CN6IO95Yqb77yI5YW25a+mIFJlc29sdmVyIOW3sue2k+aOpeaJi+S6hu+8jOmAmeihjOWPr+S7peS/neeVmeaMh+WQkSByb3V0aW5nRW5jcnlwdG9y77yb5oiW55u05o6l5LiN6Kit77yJ77yaCgoKCmphc3lwdDoKCiAgZW5jcnlwdG9yOgoKICAgIGJlYW46IHJvdXRpbmdFbmNyeXB0b3IKCgoKCgrnhLblvozlnKjntYTmhYvmqpToo6HkvaDlj6/ku6Xmt7fnlKjvvJoKCgoKIyDmlrDmoLzlvI/vvIhBRVMtMjU277yJCgpzcHJpbmc6CgogIGRhdGFzb3VyY2U6CgogICAgcGFzc3dvcmQ6ICJFTkNfTkVXKHh4eHh4eHguLi4pIgoKCgojIOiIiuagvOW8j++8iE1ENStERVPvvIkKCmxlZ2FjeToKCiAgdG9rZW46ICJFTkNfT0xEKHl5eXl5eXkuLi4pIgoKCgojIOacquaomeiomOiIiuaWsOiAhe+8iOatt+WPsumBuueVme+8iQoKb3RoZXI6CgogIHNlY3JldDogIkVOQyh6enp6enouLi4pIgoKCgrot6/nt5ogMu+8muWPquaOm+S4gOWAiyBqYXN5cHQuZW5jcnlwdG9yLmJlYW4KCgoK5aaC5p6c5Y+q5Zyo57WE5oWL5bGk6Z2i5ZWf55So5paw5ryU566X5rOV77yIamFzeXB0LmVuY3J5cHRvci5iZWFuPWVuY3J5cHRvckJlYW7vvInvvIzoiIros4fmlpnkuI3lho3nlKjmlrzntYTmhYvvvIzlsLHkuI3pnIDopoHot6/nlLHjgILoiIros4fmlpnoi6XpgoTmnIPlnKjnqIvlvI/kuK3nlKjliLDvvIzlsLHmlLnnlLHnqIvlvI/norznmoQgQFF1YWxpZmllcigibGVnYWN5RW5jcnlwdG9yIikg5L6G6Kej5Y2z5Y+v44CC6YCZ5piv5pyA57Ch5r2U77yM5L2G54Sh5rOV5ZCM5pmC5Zyo57WE5oWL5bGs5oCn6KOh6Kej5YWp56iu5qC85byP44CCCgoKCkMuIOeUouWHuuiIh+i9ieaPm+etlueVpe+8iOWvpuWLmeW7uuitsO+8iQoKCgrmlrDos4fmlpnkuIDlvovnlKggQUVTLTI1Nu+8iGVuY3J5cHRvckJlYW7vvInnlKLnlJ/vvJvpgb/lhY3lho3lr6vlhaUgTUQ1K0RFU+OAggoKCgroiIros4fmlpnkv53nlZnop6Plr4bog73lipvkuIDmrrXmmYLplpPvvIhsZWdhY3lFbmNyeXB0b3LvvInvvIzpgJDmraXovYnmqpTmiJbmlrzoroDlj5bmmYLljbPmmYLovYnlrZjngrrmlrDmoLzlvI/jgIIKCgoK5oqK57WE5oWL5YC85pS55qiZ6KiY54K6IEVOQ19ORVcoLi4uKe+8jOWDheWcqOalteerr+ebuOWuuemcgOaxguaZguaJjeS/neeVmSBFTkNfT0xEKC4uLinjgIIKCgoKRC4g5YW45Z6L6JC95Z2R5o+Q6YaSCgoKCuS9oOeahOWOn+Wni+eJh+auteacieS6m+Wwj+iqpOakje+8mmNvbmZpZy4gc2V0UGFzc3dvcmQoc2FpdCnvvIjmh4nngrogc2FsdO+8ieOAgWNvbmZpZy1zZXRBbGdvcml0aG3vvIjmh4nngrogc2V0QWxnb3JpdGht77yJ44CB5YWo5b2i5ous6Jmf6IiH56m655m9562J77yb5LiK6Z2i5o+Q5L6b55qE56iL5byP56K85bey5L+u5q2j44CCCgoKClBCRVdpdGhNRDVBbmRERVMgKyBOb0l2R2VuZXJhdG9yIOWuieWFqOaAp+W+iOW8se+8jOWPquS9nOebuOWuueacn+S9v+eUqO+8jOS4pumBv+WFjeWGjeeUqOaWvOaWsOWvhuaWh+OAggoKCgpQX0VOVl8xL1BfRU5WXzIg5L6G6IeqIEpWTSDns7vntbHlsazmgKfmiJbnkrDlooPorormlbjmmYLvvIzopoHogIPmha7nqbovbnVsbO+8jOW3suWcqOevhOS+i+iZleeQhuOAggoKCgpKQ0UgQUVTLTI1NiDlnKjoiIogSkRLIOabvumcgOOAjOeEoemZkOWItuW8t+W6puetlueVpeaqlOOAje+8m+ePvuS7oyBKREsg5bey5YWn5bu677yM5LuN5bu66K2w5Zyo5omA5pyJ55Kw5aKD6amX6K2J5LiA5qyh44CCCgoKCkUuIOacgOWwj+WPr+eUqOmFjee9rue4vee1kAoKCgpAQmVhbigibGVnYWN5RW5jcnlwdG9yIikgKyBAQmVhbigiZW5jcnlwdG9yQmVhbiIpIOWFqee1hOmDveiou+WGiuOAggoKCgrntYTmhYvlsaToi6XpnIDlkIzmmYLmlK/mj7TlhannqK7moLzlvI/vvJrliqDkuIroh6roqIIgRGV0ZWN0b3IvUmVzb2x2ZXLvvIjmiJbot6/nlLEgRW5jcnlwdG9y77yJ77yM5Lim5YWB6KixIEVOQ19ORVcoLi4uKeOAgUVOQ19PTEQoLi4uKeOAggoKCgrnqIvlvI/norzlsaTnlKggQFF1YWxpZmllciDnsr7mupbpgbjmk4fopoHnlKjlk6rkuIDntYTjgIIKCgoK5o6l5LiL5L6G6Iul5L2g6KaB77yM5oiR5Y+v5Lul55Si55Sf5LiA5pSv5bCP5ris6Kmm77yISlVuaXQg5oiW57Ch5ZauIG1haW4oKe+8ieW5q+S9oOaKiuaYjuaWh+i9ieaIkCBFTkNfTkVXKC4uLikgLyBFTkNfT0xEKC4uLikg5YWp56iu5a+G5paH77yM5pa55L6/5L2g5pu/5o+b57WE5oWL44CCCg==