当前位置: 首页 > article >正文

Android DUKPT - 3DES

 一、DUKPT概述

DUKPT 即Derived Unique Key Per Transaction(每个事务的派生唯一密钥)。ANSI X9.24规范定义的密钥管理体系,主要用于对称密钥加密场景(如MAC、PIN等敏感数据保护)。通过动态生成唯一交易密钥,解决传统固定密钥易被破解的安全风险。

二、组成结构

DUKPT由BDK及KSN组成。

  1. 基础主密钥(BDK)

    • 作为根密钥(Base Derivation Key),通过加密模块生成初始密钥

    • 通常为双倍长3DES密钥(如128位或192位

  2. 密钥序列号(KSN)

    • 包含三部分:
      • 密钥标识(10位:9位基础派生标识 + 1位子密钥标识)

      • 设备标识(5位,含二进制位扩展)

      • 交易计数器(5位,记录交易次数)

    • 确保终端密钥唯一性,防止重复

三、秘钥衍生过程

  1. 根密钥准备
    收单机构通过HSM生成双倍长3DES的BDK(如0123456789ABCDEFFEDCBA9876543210),该密钥需满足FIPS 140-2 Level 3以上安全标准。

  2. KSN结构解析
    KSN由三部分构成:

    • 设备标识:10位十六进制(如2900080124)包含厂商代码和设备序列号

    • 交易计数器:21位二进制(如00021E00001)记录交易次数

    • 扩展位:1位二进制用于标识密钥用途

  3. IPEK生成算法
    通过3DES算法对BDK和设备标识进行加密运算

  4.  动态密钥派生
    使用IPEK及KSN进行系列异或运算最终获得当前加密PIN的PEK。

四、代码实现   

        代码作者 Antoine Averlant

// 基于BDK及KSN生成当前PIN秘钥。
public static byte[] computeKeyFromBDK(byte[] baseDerivationKey, byte[] keySerialNumber) throws Exception {BitSet ksn = toBitSet(keySerialNumber);BitSet bdk = toBitSet(baseDerivationKey);BitSet ipek = getIpek(bdk, ksn);// convert key for returningBitSet key = _getCurrentKey(ipek, ksn);byte[] rkey = toByteArray(key);// secure memoryobliviate(ksn);obliviate(bdk);obliviate(ipek);obliviate(key);return rkey;
}// 基于BDK及KSN生成Ipek
public static BitSet getIpek(BitSet key, BitSet ksn) throws Exception {byte[][] ipek = new byte[2][];BitSet keyRegister = key.get(0, key.length());BitSet data = ksn.get(0, ksn.length());data.clear(59, 80);ipek[0] = encryptTripleDes(toByteArray(keyRegister), toByteArray(data.get(0, 64)));keyRegister.xor(toBitSet(toByteArray("C0C0C0C000000000C0C0C0C000000000")));ipek[1] = encryptTripleDes(toByteArray(keyRegister), toByteArray(data.get(0, 64)));byte[] bipek = concat(ipek[0], ipek[1]);BitSet bsipek = toBitSet(bipek);// secure memoryobliviate(ipek[0]);obliviate(ipek[1]);obliviate(bipek);obliviate(keyRegister);obliviate(data);return bsipek;
}// 基于IPEK及KSN生成PEK
private static BitSet _getCurrentKey(BitSet ipek, BitSet ksn) throws Exception {BitSet key = ipek.get(0, ipek.length());BitSet counter = ksn.get(0, ksn.length());counter.clear(59, ksn.length());for (int i = 59; i < ksn.length(); i++) {if (ksn.get(i)) {counter.set(i);BitSet tmp = _nonReversibleKeyGenerationProcess(key, counter.get(16, 80));// secure memoryobliviate(key);key = tmp;}}key.xor(toBitSet(toByteArray("00000000000000FF00000000000000FF"))); // data encryption variant (To PIN)// key.xor(toBitSet(toByteArray("F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0"))); // data encryption variant// key.xor(toBitSet(toByteArray("3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C"))); // data encryption variant// secure memoryobliviate(counter);return key;
}private static BitSet _nonReversibleKeyGenerationProcess(BitSet p_key, BitSet data) throws Exception {BitSet keyreg = p_key.get(0, p_key.length());BitSet reg1 = data.get(0, data.length());// step 1: Crypto Register-1 XORed with the right half of the Key Register goes to Crypto Register-2.BitSet reg2 = reg1.get(0, 64); // reg2 is being used like a temp herereg2.xor(keyreg.get(64, 128));   // and here, too, kind of// step 2: Crypto Register-2 DEA-encrypted using, as the key, the left half of the Key Register goes to Crypto Register-2reg2 = toBitSet(encryptDes(toByteArray(keyreg.get(0, 64)), toByteArray(reg2)));// step 3: Crypto Register-2 XORed with the right half of the Key Register goes to Crypto Register-2reg2.xor(keyreg.get(64, 128));// done messing with reg2// step 4: XOR the Key Register with hexadecimal C0C0 C0C0 0000 0000 C0C0 C0C0 0000 0000keyreg.xor(toBitSet(toByteArray("C0C0C0C000000000C0C0C0C000000000")));// step 5: Crypto Register-1 XORed with the right half of the Key Register goes to Crypto Register-1reg1.xor(keyreg.get(64, 128));// step 6: Crypto Register-1 DEA-encrypted using, as the key, the left half of the Key Register goes to Crypto Register-1reg1 = toBitSet(encryptDes(toByteArray(keyreg.get(0, 64)), toByteArray(reg1)));// step 7: Crypto Register-1 XORed with the right half of the Key Register goes to Crypto Register-1reg1.xor(keyreg.get(64, 128));// donebyte[] reg1b = toByteArray(reg1), reg2b = toByteArray(reg2);byte[] key = concat(reg1b, reg2b);BitSet rkey = toBitSet(key);// secure memoryobliviate(reg1);obliviate(reg2);obliviate(reg1b);obliviate(reg2b);obliviate(key);obliviate(keyreg);return rkey;
}public static byte[] encryptTripleDes(byte[] key, byte[] data) throws Exception {return encryptTripleDes(key, data, true);
}public static byte[] encryptTripleDes(byte[] key, byte[] data, boolean padding) throws Exception {BitSet bskey = toBitSet(key);BitSet k1, k2, k3;if (bskey.length() == 64) {// single lengthk1 = bskey.get(0, 64);k2 = k1;k3 = k1;} else if (bskey.length() == 128) {// double lengthk1 = bskey.get(0, 64);k2 = bskey.get(64, 128);k3 = k1;} else {// triple lengthif (bskey.length() != 192) {throw new InvalidParameterException("Key is not 8/16/24 bytes long.");}k1 = bskey.get(0, 64);k2 = bskey.get(64, 128);k3 = bskey.get(128, 192);}byte[] kb1 = toByteArray(k1), kb2 = toByteArray(k2), kb3 = toByteArray(k3);byte[] key16 = concat(kb1, kb2);byte[] key24 = concat(key16, kb3);IvParameterSpec iv = new IvParameterSpec(new byte[8]);SecretKey encryptKey = SecretKeyFactory.getInstance("DESede").generateSecret(new DESedeKeySpec(key24));Cipher encryptor;if (padding)encryptor = Cipher.getInstance("DESede/CBC/PKCS5Padding");elseencryptor = Cipher.getInstance("DESede/CBC/NoPadding");encryptor.init(Cipher.ENCRYPT_MODE, encryptKey, iv);byte[] bytes = encryptor.doFinal(data);// secure memoryobliviate(k1);obliviate(k2);obliviate(k3);obliviate(kb1);obliviate(kb2);obliviate(kb3);obliviate(key16);obliviate(key24);obliviate(bskey);return bytes;
}public static byte[] MEKQ(byte[] bPEK) throws Exception {BitSet pek = toBitSet(bPEK);pek.xor(toBitSet(toByteArray("000000000000FFFF000000000000FFFF")));byte[] rkey = toByteArray(pek);// secure memoryobliviate(pek);return rkey;
}// 核心MAC计算逻辑 ISO-9797-PART1public static byte[] calculateMac(byte[] keyData, byte[] inputData) throws Exception {// 1. 处理双倍长密钥(16字节 -> 24字节)byte[] fullKey = Arrays.copyOf(keyData, 24);byte[] key1 = new byte[16];System.arraycopy(fullKey, 0, fullKey, 16, 8);System.arraycopy(keyData, 0, key1, 0, 8);System.arraycopy(keyData, 0, key1, 8, 8);// 2. 初始化加密器(CBC模式 + 零向量)Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key1, "DESede"), new IvParameterSpec(new byte[8]));// 3. 数据填充(补零至8字节倍数)byte[] paddedData;if (inputData.length % 8 != 0) {paddedData = Arrays.copyOf(inputData, inputData.length + (8 - inputData.length % 8));} else {paddedData = Arrays.copyOf(inputData, inputData.length);}int count = paddedData.length/8;byte[] paddedBlock = new byte[8];byte[] encryptData = new byte[8];//拆分数据--加密--异或for (int i = 0; i < count; i++) {if (i == 0) {System.arraycopy(paddedData, 0, paddedBlock, 0, 8);}encryptData = cipher.doFinal(paddedBlock);if (i + 2 < count) {System.arraycopy(paddedData, (i+1) * 8, paddedBlock, 0, 8);myXor(paddedBlock, encryptData);} else {System.arraycopy(paddedData, (i+1) * 8, paddedBlock, 0, 8);myXor(paddedBlock, encryptData);break;}}cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(fullKey, "DESede"), new IvParameterSpec(new byte[8]));encryptData = cipher.doFinal(paddedBlock);// 4. 执行加密并取前8字节return Arrays.copyOfRange(encryptData, 0, encryptData.length);
}public static void myXor(byte[] a, byte[] b) {if (a.length != b.length)return;for (int i = 0; i < a.length; i++)a[i] ^= b[i];
}/*** Converts a byte array to an extended BitSet.*/public static BitSet toBitSet(byte[] b) {BitSet bs = new BitSet(8 * b.length);for (int i = 0; i < b.length; i++) {for (int j = 0; j < 8; j++) {if ((b[i] & (1L << j)) > 0) {bs.set(8 * i + (7 - j));}}}return bs;}/*** Converts an extended BitSet into a byte.* <p>* Requires that the BitSet be exactly 8 bits long.*/public static byte toByte(BitSet b) {byte value = 0;for (int i = 0; i < b.length(); i++) {if (b.get(i))value = (byte) (value | (1L << 7 - i));}return value;}/*** Converts a BitSet into a byte array.* <p>* Pads to the left with zeroes.*/public static byte[] toByteArray(BitSet b) {int size = (int) Math.ceil(b.length() / 8.0d);byte[] value = new byte[size];for (int i = 0; i < size; i++) {value[i] = toByte(b.get(i * 8, Math.min(b.length(), (i + 1) * 8)));}return value;}/*** Converts a hexadecimal String into a byte array (Big-Endian).** @param s A representation of a hexadecimal number without any leading qualifiers such as "0x" or "x".*/public static byte[] toByteArray(String s) {int len = s.length();byte[] data = new byte[len / 2];for (int i = 0; i < len; i += 2) {data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));}return data;}/*** Converts a byte array into a hexadecimal string (Big-Endian).** @return A representation of a hexadecimal number without any leading qualifiers such as "0x" or "x".*/public static String toHex(byte[] bytes) {BigInteger bi = new BigInteger(1, bytes);return String.format("%0" + (bytes.length << 1) + "X", bi);}/*** Concatenates two byte arrays.** @return The array a concatenated with b.  So if r is the returned array, r[0] = a[0] and r[a.length] = b[0].*/public static byte[] concat(byte[] a, byte[] b) {byte[] c = new byte[a.length + b.length];for (int i = 0; i < a.length; i++) {c[i] = a[i];}for (int i = 0; i < b.length; i++) {c[a.length + i] = b[i];}return c;}/*** Overwrites the extended BitSet NUM_OVERWRITES times with random data for security purposes.*/public static void obliviate(BitSet b) {obliviate(b, NUM_OVERWRITES);}/*** Overwrites the byte array NUM_OVERWRITES times with random data for security purposes.*/public static void obliviate(byte[] b) {obliviate(b, NUM_OVERWRITES);}/*** Overwrites the extended BitSet with random data for security purposes.*/public static void obliviate(BitSet b, int n) {java.security.SecureRandom r = new java.security.SecureRandom();for (int i = 0; i < NUM_OVERWRITES; i++) {for (int j = 0; j < b.length(); j++) {b.set(j, r.nextBoolean());}}}/*** Overwrites the byte array with random data for security purposes.*/public static void obliviate(byte[] b, int n) {for (int i = 0; i < n; i++) {b[i] = 0x00;b[i] = 0x01;}java.security.SecureRandom r = new java.security.SecureRandom();for (int i = 0; i < n; i++) {r.nextBytes(b);}}

相关文章:

Android DUKPT - 3DES

一、DUKPT概述 DUKPT 即Derived Unique Key Per Transaction&#xff08;每个事务的派生唯一密钥&#xff09;。ANSI X9.24规范定义的密钥管理体系&#xff0c;主要用于对称密钥加密场景&#xff08;如MAC、PIN等敏感数据保护&#xff09;。通过动态生成唯一交易密钥&#xff…...

机器学习数学基础:45.多重响应分析

多重响应分析超详细教程&#xff1a;手把手教你分析多选题数据 一、深入理解多重响应分析的背景 问卷调查中&#xff0c;问题分为单选题与多选题&#xff1a; 单选题&#xff1a;如“你的性别&#xff1f;1.男 2.女”&#xff0c;答题者仅选一个选项&#xff0c;分析时直接统…...

《苍穹外卖》SpringBoot后端开发项目核心知识点与常见问题整理(DAY1 to DAY3)

目录 一、在本地部署并启动Nginx服务1. 解压Nginx压缩包2. 启动Nginx服务3. 验证Nginx是否启动成功&#xff1a; 二、导入接口文档1. 黑马程序员提供的YApi平台2. YApi Pro平台3. 推荐工具&#xff1a;Apifox 三、Swagger1. 常用注解1.1 Api与ApiModel1.2 ApiModelProperty与Ap…...

企业安全—对数据和资产进行识别和分类

0x00 前言 针对数据和资产的保护刻不容缓&#xff0c;这个是每一个做企业安全建设不容放过的一环&#xff0c;那么在识别数据和资产已经对这些数据分类就是必须要了解和掌握的内容。 这里不仅是针对商业机密&#xff0c;还有用户数据&#xff0c;前者在于保护公司利益&#x…...

QT系列教程(20) Qt 项目视图便捷类

视频连接 https://www.bilibili.com/video/BV1XY41127t3/?vd_source8be9e83424c2ed2c9b2a3ed1d01385e9 Qt项目视图便捷类 Qt项目视图提供了一些便捷类&#xff0c;包括QListWidget, QTableWidget&#xff0c; QTreeWidget等。我们分别介绍这几个便捷类。 我们先创建一个Qt …...

Spring Boot 调用DeepSeek API的详细教程

目录 前置准备步骤1&#xff1a;创建Spring Boot项目步骤2&#xff1a;配置API参数步骤3&#xff1a;创建请求/响应DTO步骤4&#xff1a;实现API客户端步骤5&#xff1a;创建控制器步骤6&#xff1a;异常处理步骤7&#xff1a;测试验证单元测试示例Postman测试请求 常见问题排查…...

动态扩缩容引发的JVM堆内存震荡:从原理到实践的GC调优指南

目录 一、典型案例&#xff1a;系统发布后的GC雪崩事件 &#xff08;一&#xff09;故障现象 1. 刚刚启动时 GC 次数较多 2. 堆内存锯齿状波动 3. GC日志特征&#xff1a;Allocation Failure &#xff08;二&#xff09;问题定位 二、原理深度解析&#xff1a;JVM内存弹…...

AI智能眼镜主控芯片:技术演进与产业生态的深度解析

一、AI智能眼镜的技术挑战与主控芯片核心诉求 AI智能眼镜作为XR&#xff08;扩展现实&#xff09;技术的代表产品&#xff0c;其核心矛盾在于性能、功耗与体积的三角平衡。主控芯片作为设备的“大脑”&#xff0c;需在有限空间内实现复杂计算、多模态交互与全天候续航&#xf…...

微服务拆分-远程调用

我们在查询购物车列表的时候&#xff0c;它有一个需求&#xff0c;就是不仅仅要查出购物车当中的这些商品信息&#xff0c;同时还要去查到购物车当中这些商品的最新的价格和状态信息&#xff0c;跟购物车当中的快照进行一个对比&#xff0c;从而去提醒用户。 现在我们已经做了服…...

[网络爬虫] 动态网页抓取 — Selenium 介绍 环境配置

&#x1f31f;想系统化学习爬虫技术&#xff1f;看看这个&#xff1a;[数据抓取] Python 网络爬虫 - 学习手册-CSDN博客 0x01&#xff1a;Selenium 工具介绍 Selenium 是一个开源的便携式自动化测试工具。它最初是为网站自动化测试而开发的&#xff0c;类似于我们玩游戏用的按…...

【RAGFlow】windows本地pycharm运行

原因 由于官方只提供了docker部署&#xff0c;基于开源代码需要实现自己内部得逻辑&#xff0c;所以需要本地pycharm能访问&#xff0c;且docker运行依赖得其余组件&#xff0c;均需要使用开发服务器得配置。 修改过程 安装python 项目依赖于Python 版本&#xff1a;>3.1…...

git子仓库管理的两种方式

在团队协作中选择使用 Git Submodule 还是 Git Subtree 取决于项目的需求和团队的工作方式。以下是两者的对比和适用场景分析&#xff0c;帮助你做出选择&#xff1a; Git Submodule 优点 独立性高 子模块是一个独立的仓库&#xff0c;拥有自己的提交历史和分支。这使得子模…...

树莓派5首次开机保姆级教程(无显示器通过VNC连接树莓派桌面)

第一次开机详细步骤 步骤一&#xff1a;树莓派系统烧录1 搜索打开烧录软件“Raspberry Pi Imager”2 选择合适的设备、系统、SD卡3 烧录配置选项 步骤二&#xff1a;SSH远程树莓派1 树莓派插电2 网络连接&#xff08;有线或无线&#xff09;3 确定树莓派IP地址 步骤三&#xff…...

html-表格标签

一、表格标签 1. 表格的主要作用 表格主要用于显示&#xff64;展示数据,因为它可以让数据显示的非常的规整,可读性非常好&#xff61;特别是后台展示数据 的时候,能够熟练运用表格就显得很重要&#xff61;一个清爽简约的表格能够把繁杂的数据表现得很有条理&#xff61; 总…...

大模型安全新范式:DeepSeek一体机内容安全卫士发布

2月以来&#xff0c;DeepSeek一体机几乎成为了政企市场AI消费的最强热点。 通过一体机的方式能够缩短大模型部署周期&#xff0c;深度结合业务场景&#xff0c;降低中小企业对于大模型的使用门槛。据不完全统计&#xff0c;已约有超过60家企业基于DeepSeek推出一体机产品。 但…...

数据分析绘制随时间顺序变化图加入线性趋势线——numpy库的polyfit计算一次多项式拟合

import pandas as pd import numpy as np import matplotlib.pyplot as plt# 导入数据 data pd.read_csv(rC:\Users\11712\notebooktrain1.csv)# 假设数据包含 date_time 和 speed 列 data[date_time] pd.to_datetime(data[date_time]) # 确保时间列是 datetime 类型 data.s…...

密闭空间可燃气体监测终端:守护城市命脉,智驭燃气安全!

近年来&#xff0c;陕西省高度重视燃气安全&#xff0c;出台了一系列政策文件&#xff0c;旨在全面加强城镇燃气安全监管&#xff0c;防范化解重大安全风险。2023年&#xff0c;陕西省安委会印发《全省城镇燃气安全专项整治工作方案》&#xff0c;明确要求聚焦燃气经营、输送配…...

阿里千问大模型(Qwen2.5-VL-7B-Instruct)部署

参考链接 知乎帖子 B站视频 huggingface 镜像网站&#xff08;不太全&#xff0c;比如 Qwen/Qwen2.5-VL-7B-Instruct就没有&#xff09; huggingface 5种下载方式汇总 通过huggingface-cli下载模型 不一样的部分是预训练权重的下载和demo 首先安装huggingface_hub pip insta…...

【人工智能】随机森林的智慧:集成学习的理论与实践

随机森林(Random Forest)是一种强大的集成学习算法,通过构建多棵决策树并结合投票或平均预测提升模型性能。本文深入探讨了随机森林的理论基础,包括决策树的构建、Bagging方法和特征随机选择机制,并通过LaTeX公式推导其偏差-方差分解和误差分析。接着,我们详细描述了随机…...

Javascript基础语法详解

面向对象的语言.脚本语言,不需要编译,浏览器解释即可运行 .用于控制网页的行为.浏览器的source可以打断点调试, console输入代码可以执行 use strict指令: 在“严格模式”下运行js代码, 防止意外创建全局变量等, 提高代码安全性和执行效率. 使用: 全局严格模式&#xff1a;…...

前端状态管理 pinia和vuex高频面试题

前端状态管理 Pinia 和 Vuex 是 Vue 生态中常用的状态管理方案&#xff0c;在面试中经常涉及 基本概念、对比、最佳实践、性能优化 等多个方面。以下是 高频面试题 详细答案&#xff0c;共 20 题&#xff0c;助你轻松应对面试&#xff01;&#x1f680; &#x1f525; 基础概念…...

【Go学习实战】03-3-文章评论及写文章

【Go学习实战】03-3-文章评论及写文章 文章评论注册valine获取凭证加载评论页面 写文章修改cdn位置完善功能查看页面 发布文章POST发布文章发布文章测试 查询文章详情查询详情测试 修改文章修改文章测试 写文章图片上传前端后端逻辑测试 文章评论 这里我们的博客因为是个轻量级…...

从零开始用AI开发游戏(一)

1. 核心玩法设计 核心目标&#xff1a;玩家需在随机生成的3D迷宫中寻找出口&#xff0c;躲避陷阱、收集道具、解开谜题。核心机制&#xff1a; 随机生成迷宫&#xff1a;每次游戏生成不同结构的迷宫&#xff08;递归分割算法或深度优先搜索&#xff09;。第一人称视角&#xf…...

AI-大模型中的流式输出与非流式输出

1.前言 在大模型API开发中&#xff0c;流式与非流式输出对应着两种不同的数据交互&#xff0c;在代码中stream中通过参数true与false来进行设定。 2.流式输出与非流式输出的原理 2.1.非流式输出-请求一次响应返回完整数据 非流式输出&#xff0c;传统的请求-响应模式&#xf…...

【HarmonyOS Next】鸿蒙加固方案调研和分析

【HarmonyOS Next】鸿蒙加固方案调研和分析 一、前言 根据鸿蒙应用的上架流程&#xff0c;本地构建app文件后&#xff0c;上架到AGC平台&#xff0c;平台会进行解析。根据鸿蒙系统的特殊设置&#xff0c;仿照IOS的生态闭环方案。只能从AGC应用市场下载app进行安装。这样的流程…...

树莓集团现状最新进展:宜宾园区业务有何新突破​

树莓集团宜宾园区在当下取得了令人瞩目的最新进展和新突破。在技术创新方面&#xff0c;园区加大研发投入&#xff0c;成功攻克了多项关键技术难题。 例如&#xff0c;在人工智能图像识别技术上取得重大突破&#xff0c;该技术已应用于园区内的智能安防系统和工业生产检测环节…...

蓝桥杯javaB组备战第二天 题目 区间次方和 编号3382

这是一个前缀和问题&#xff0c;但是不同于以为前缀和问题 前缀和问题求解思路&#xff1a; 创建一个前缀数组 s[] ,存储输入的元素的a[1]到a[n]的和 及&#xff1a;s[1] s[i-1]a[i] ,i>1 这样比暴力算法的复杂度要低很多可以将 时间复杂度从O(q*n*m)下降到 O(n*mq) …...

SpringBoot设置过滤器(Filter)或拦截器(Interceptor)的执行顺序:@Order注解、setOrder()方法

Java Web 过滤器、拦截器、监听器,系列文章: (1)过滤器(Filter)的使用: 《Servlet过滤器(Filter)的使用:Filter接口、@WebFilter注释》 《SpringMVC使用过滤器(Filter)解决中文乱码》 《SpringBoot过滤器(Filter)的使用:Filter接口、FilterRegistrationBean类配…...

【git】补丁文件

项目中总有一些本地修改是既不能上传到远程分支又不能直接加入到.gitignore文件中的。 固然可以使用stash但它毕竟只是一种临时保存更改的机制&#xff0c;更适用于本地开发过程中需要频繁切换任务的场景。 如果想要共享代码更改&#xff0c;那还是补丁文件更合适一些。git d…...

linux自启动服务

在Linux环境中&#xff0c;systemd是一个系统和服务管理器&#xff0c;它为每个服务使用.service文件进行配置。systemctl是用于控制系统服务的主要工具。本文将详细介绍如何使用systemctl来管理vsftpd服务&#xff0c;以及如何设置服务自启动。 使用Systemd设置自启动服务 创…...