jenkins凭证解密

由于某种原因,我们需要对jenkins存储的凭证进行解密.

我们需要使用三个文件,分别是

  1. secrets目录下的master.key
  2. secrets目录下的hudson.util.Secret
  3. home目录下的credentials.xml,这里存储了密文密码,如下
<com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
  <scope>GLOBAL</scope>
  <id>xxx</id>
  <description>xxxx</description>
  <username>xxx</username>
  <password>密文密码</password>
</com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>

其中,password 里的用{}包裹的就是我们要用的,把大括号一起复制到下面代码里

代码如下,使用了依赖

      <dependency>
          <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
package com.xiuluo.test.jenkins;

import org.apache.commons.io.IOUtils;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * 文件名称:Test<br>
 * 初始作者:修罗大人<br>
 * 创建日期:2020-04-21 14:28<br>
 * 功能说明:<br>
 * <br>
 */
public class Test {

    private static final String KEY_ALGORITHM = "AES";
    private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
    private final String rootDir = "secrets文件夹路径";
    private static final byte[] MAGIC = "::::MAGIC::::".getBytes();
    // 密文密码
    private static final String data = "密文密码";


    @org.junit.Test
    public void decrypt() {

        byte[] payload;
        try {
            payload = Base64.getDecoder().decode(data.substring(1, data.length()-1));
        } catch (IllegalArgumentException e) {
            return;
        }
        switch (payload[0]) {
            case 1:
                // For PAYLOAD_V1 we use this byte shifting model, V2 probably will need DataOutput
                int ivLength = ((payload[1] & 0xff) << 24)
                        | ((payload[2] & 0xff) << 16)
                        | ((payload[3] & 0xff) << 8)
                        | (payload[4] & 0xff);
                int dataLength = ((payload[5] & 0xff) << 24)
                        | ((payload[6] & 0xff) << 16)
                        | ((payload[7] & 0xff) << 8)
                        | (payload[8] & 0xff);
                if (payload.length != 1 + 8 + ivLength + dataLength) {
                    // not valid v1
                    return;
                }
                byte[] iv = Arrays.copyOfRange(payload, 9, 9 + ivLength);
                byte[] code = Arrays.copyOfRange(payload, 9+ivLength, payload.length);
                String text;
                try {
                    text = new String(decrypt(iv).doFinal(code), UTF_8);
                    System.out.println("密码明文:" + text);
                } catch (GeneralSecurityException e) {
                    // it's v1 which cannot be historical, but not decrypting
                    return;
                }
//                return new Secret(text, iv);
            default:
                return;
        }

    }

    public Cipher decrypt(byte[] iv) {
        try {
            Cipher cipher = getCipher(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, getKey(), new IvParameterSpec(iv));
            return cipher;
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }

    private synchronized SecretKey getKey() throws Exception {
        SecretKey secret  = null;
            try {
                byte[] payload = load();
                if (payload == null) {
                    payload = randomBytes(256);
//                    store(payload);
                }
                // Due to the stupid US export restriction JDK only ships 128bit version.
                secret = new SecretKeySpec(payload, 0, 128 / 8, KEY_ALGORITHM);
            } catch (IOException e) {
                throw e;
            }
        return secret;
    }

    protected byte[] load() throws Exception {
        try {
            File f = new File(rootDir,"hudson.util.Secret");
            if (!f.exists())    return null;

            Cipher sym = getCipher("AES");
            sym.init(Cipher.DECRYPT_MODE, getMasterKey());
            try (InputStream fis= Files.newInputStream(f.toPath());
                 CipherInputStream cis = new CipherInputStream(fis, sym)) {
                byte[] bytes = IOUtils.toByteArray(cis);
                return verifyMagic(bytes);
            }
        } catch (Exception x) {
            if (x.getCause() instanceof BadPaddingException) {
                throw x; // broken somehow
            } else {
                throw x;
            }
        }
    }

    public static Cipher getCipher(String algorithm) throws GeneralSecurityException {
        return Cipher.getInstance(algorithm);
    }

    private SecretKey getMasterKey() throws Exception {
        File file = new File(rootDir,"master.key");
        SecretKey masterKey = toAes128Key(read(file).trim());

        return masterKey;
    }

    public String read(File file) throws Exception {
        StringWriter out = new StringWriter();
        PrintWriter w = new PrintWriter(out);
        BufferedReader in = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8);
        String line;
        while ((line = in.readLine()) != null)
            w.println(line);
        return out.toString();
    }

    public byte[] randomBytes(int size) {
        byte[] random = new byte[size];
        new SecureRandom().nextBytes(random);
        return random;
    }

    private byte[] verifyMagic(byte[] payload) {
        int payloadLen = payload.length-MAGIC.length;
        if (payloadLen<0)   return null;    // obviously broken

        for (int i=0; i<MAGIC.length; i++) {
            if (payload[payloadLen+i]!=MAGIC[i])
                return null;    // broken
        }
        byte[] truncated = new byte[payloadLen];
        System.arraycopy(payload,0,truncated,0,truncated.length);
        return truncated;
    }

    public static String toHexString(byte[] bytes) {
        int start = 0;
        int len = bytes.length;

        StringBuilder buf = new StringBuilder();
        for( int i=0; i<len; i++ ) {
            int b = bytes[start+i]&0xFF;
            if(b<16)    buf.append('0');
            buf.append(Integer.toHexString(b));
        }
        return buf.toString();
    }

    public static SecretKey toAes128Key(String s) {
        try {
            // turn secretKey into 256 bit hash
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            digest.reset();
            digest.update(s.getBytes(StandardCharsets.UTF_8));

            // Due to the stupid US export restriction JDK only ships 128bit version.
            return new SecretKeySpec(digest.digest(),0,128/8, "AES");
        } catch (NoSuchAlgorithmException e) {
            throw new Error(e);
        }
    }

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容