Android混淆那些事

前言

作为一个Android开发,大家或多或少都有一些关于混淆的了解(毕竟披个纱布也比裸奔要好的多吧)。混淆的概念虽然容易理解,但相信大多数开发可能还是在网上搜索通用配置后通过C-V大法接入到自己的项目中,这也使得混淆配置比较混乱,缺乏针对性。

来吧,让我们看看怎么才能穿好这件衣服!!

 

混淆的必要性

Java是一种广泛使用的计算机编程语言,拥有跨平台、面向对象、泛型编程的特性。但不同于一般的编译语言或解释型语言,它首先将源代码编译成字节码,再依赖各种不同平台上的虚拟机来解释执行字节码,从而具有“一次编写,到处运行”的跨平台特性。而这些字节码带有许多的语义信息,很容易被反编译成Java源代码。

WTFK!!

那么作为使用Java作为编程语言(别较劲,我知道Kotlin)的Android应用怎么办,不是动不动就被反编译还原啦!!没错,如果你不做些保护措施,那你的APP、SDK在别人眼里就是那么的赤果果。

混淆就是你现在需要的一顶保护伞。

混淆就是将原本正常的项目文件,对其类、方法、字段,重新命名a,b,c…之类的字母,使得处理后的代码在实现相同功能的同时,降低阅读性,从而增加反编译的难度。但是,但是,但是,重要的事情说三遍,混淆只能增加反编译的难度并不能保证觉得的安全,比较你也懂得,总有人的技术比你。。。。。。

 

ProGuard

官网:https://stuff.mit.edu/afs/sipb/project/android/sdk/android-sdk-linux/tools/proguard/docs/index.html#manual/introduction.html

在官网上是这样说的:
ProGuard is a Java class file shrinker, optimizer, obfuscator, and preverifier. The shrinking step detects and removes unused classes, fields, methods, and attributes. The optimization step analyzes and optimizes the bytecode of the methods. The obfuscation step renames the remaining classes, fields, and methods using short meaningless names. These first steps make the code base smaller, more efficient, and harder to reverse-engineer. The final preverification step adds preverification information to the classes, which is required for Java Micro Edition or which improves the start-up time for Java 6.

简单来说就是:
ProGuard 是一款针对于Java class的压缩、优化、混淆以及预校验工具;压缩环节检测并删除未使用的类、字段、方法和属性;优化环节分析和优化方法的字节码;混淆环节使用无意义的短名称重命名剩余的类、字段和方法。这些操作使代码库更小、更高效并且更难进行逆向工程。最后的预验证环节将预验证信息添加到类中,这是 Java Micro Edition 或者 Java 6及高版本所必需的,当然这一环节在Android开发者并不需要。

如流程图所示,ProGuard主要包含四个环节:压缩、优化、混淆、预检验;
在这里插入图片描述

  • 压缩(Shrink):检测并删除未使用的类、字段、方法和属性;
  • 优化(Optimize):优化字节码并删除未使用的指令;
  • 混淆(Obfuscate):使用无意义的短名称重命名剩余的类、字段和方法;
  • 预预检验(Preverify):对 class 文件进行预检验,确保虚拟机加载的 class 文件是安全并且可以执行的;

 

Android使用ProGuard

Android混淆说明:https://developer.android.com/studio/build/shrink-code#keep-code

Android使用ProGuard手册:https://www.guardsquare.com/manual/home

在Android Gradle 插件 3.4.0 或更高版本构建项目时,已不再使用 ProGuard 执行编译时代码优化,而是与 R8 编译器协同工作,完成编译任务。若想不想启用R8则可在gradle.properties中添加如下配置:

android.enableR8=false
android.enableR8.libraries=false

在正式使用ProGuard之前先让我们用几张图来直观的感受一下混淆的效果:

混淆前
在这里插入图片描述

混淆后
在这里插入图片描述

对比结果:

  • 包体积缩减,因为作为示例的SDK本身内容优先,资源文件极少,所以这里看来效果不明显,各位看官可以在自己的项目中看下实际的缩减效果;
  • 包名、类名、方法名替换为无意义的字母,增加理解难度;

既然看过了对比效果,那接下来就让我们撸起袖子开干吧。

 

开启ProGuard

新建module的时候系统会默认在build.gradle中添加以下配置,以开启混淆:

android {...buildTypes {release {//true - 开启混淆;false - 关闭混淆minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}
}

minifyEnabled:混淆开关;

proguardFiles:混淆配置文件列表;

getDefaultProguardFile:获取系统提供的默认混淆配置文件,文件路径为:\sdk\tools\proguard\proguard-android.txt;系统还提供了“proguard-android-optimize.txt”默认配置,如果在SDK中开启混淆这里建议使用“proguard-android.txt”;

proguard-rules.pro:编译器自动创建的配置文件,无内容,用于编写我们自定义的混淆规则;

小技巧:当针对SDK开启混淆可通过下面的设置将混淆配置直接打进SDK中,避免使用者对于SDK混淆配置的依赖:

android {defaultConfig{//将混淆配置打包进aarconsumerProguardFiles 'proguard-rules.pro'}
}

 

混淆规则

混淆开启后,编译器将根据配置文件进行处理,而需要我们自定义制定的规则则是需要明确告诉编译器哪些是我们需要保留的(编译器:老哥啊,你不告诉我哪些要留着,我怎么制定哪些是你需要的,我当然一股脑全给干了啊,毕竟我可是一个尽职尽责的编译器!!!);

下面让我们来看看一些关键的规则配置:

 

keep关键字

 

-keep [,modifier,…] class_specification

指定要保留的类和类成员(字段和方法),类不会被移除,但类成员若不声明保留且未被使用的话将会被移除(SDK开发尤其需要注意这一点);

例:

正确配置:

错误配置:

 

-keepclassmembers [,modifier,…] class_specification

指定要保留的类成员,其他类成员将会被混淆或移除,类名将会混淆,若类未被引用也将被移除;

例:

声明保留static属性及public构造方法;
在这里插入图片描述

可以看到,非static的name属性被混淆,test方法未引用被移除;
在这里插入图片描述

 

-keepclasseswithmembers [,modifier,…] class_specification

当指定的类成员都存在时,保留类和类成员,否则将被混淆或移除;

例:
声明保留具有指定构造方法及keepclasseswithmembers()方法的类

满足条件的类Keepclasseswithmembers1被保留,Keepclasseswithmembers2因缺少对应的构造方法不满足要求被移除
在这里插入图片描述

 

-keepnames、-keepclassmembernames、-keepclasseswithmembernames

这是三个规则对应 -keep(-keepclassmembers、-keepclasseswithmembers) allowshrinking class_specification 的缩写,即在原有混淆基础上若类或类成员未被使用则在压缩阶段允许移除;

 

keep规则总结对比

关键字目标对象说明
keep类、类成员保留类和类中的成员,防止它们被混淆或移除;
keepclassmembers类成员保留类成员,防止它们被混淆或移除
keepclasseswithmembers类、类成员指定的类成员都存在时,保留类和类成员,防止它们被混淆或移除
keepnames类、类成员保留类和类中的成员,防止它们被混淆,但当类或成员未被引用时会被移除;
keepclassmembernames类成员保留类成员,防止它们被混淆,但当类成员未被引用时会被移除;
keepclasseswithmembernames类、类成员指定的类成员都存在时,保留类和类成员,防止它们被混淆,但当类未被引用时会被移除;
从表格中我们可以知道每个规则都有自己针对的目标,我们必须根据实际场景来选择对应的关键字进行组合使用;尤其是必须选取合适的入口类进行keep,否则可能导致其他规则配置因为类或类成员未引用而被移除导致混淆配置无效;

 

通配符

通配符意义
<init>匹配任何构造函数
<fields>匹配任何字段
<methods>匹配任何方法
?匹配任何单个字符
%匹配任何基本类型(“ boolean”、“ int”等)或“ void”类型
  •   |	匹配任意长度字符,但是不包括类分隔符“.”
    
  •   |  	 匹配任意长度字符,包括类分隔符“.”
    

*** | 匹配任何类型(原始或非原始、数组或非数组)
… | 匹配任意数量的任意类型的参数

 

其他常见规则

 

-printmapping [filename]

指定生成混淆映射mapping文件;

 

-keepattributes[属性过滤器]

指定要保留的任何可选属性。属性可以指定一个或多个,如:

#保留Annotation不混淆
-keepattributes *Annotation*,InnerClasses
#避免混淆泛型
-keepattributes Signature
#抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable

 

-dontwarn [class_filter]

指定不警告未解决的引用和其他问题;
引入三方library时,强烈建议配置相关包名,如:

#比如关闭Twitter sdk的警告,我们可以这样做
-dontwarn com.twitter.sdk.**

 

-allowaccessmodification

允许修改扩大访问修饰符,如default扩大为public,但是并不推荐配置该规则(尤其是SDK),因为这可能导致其他实现场景中的方法冲突;
还记得开头我们说的getDefaultProguardFile获取默认配置文件吗,正式因为这条规则的存在我并不推荐使用“proguard-android-optimize.txt”;

 

-dontshrink

关闭压缩,默认开启;
默认情况下,因为类或类成员未引用时将会被移除,但是程序猿小哥说:我不想管那么多,我只知道这些类和成员不能混淆,其他你(编译器)看着办;
这时候我们就只能加上这个配置了,保留所有类和成员只做Obfuscate(混淆);

 

-repackageclasses [package_name]

指定重新打包所有重命名的类文件,将它们移动到给定的包中;

 

-keepparameternames

保留参数名称和方法类型;

 

混淆产物

每次打包完成后将在 app/build/outputs/mapping/{flavor}/ 目录下生成一些混淆相关的文件;

文件名作用
configuration.txt所有混淆配置的汇总
mapping.txt原始与混淆过的类、方法、字段名称间的转换
resources.txt资源优化记录文件,哪些资源引用了其他资源,哪些资源在使用,哪些资源被移除
seeds.txt未进行混淆的类与成员
usage.txtAPK中移除的代码

 

哪些不应该混淆

  • 自定义控件不混淆;
  • 枚举类不混淆;
  • 第三方库中的类不混淆,需要根据三方的混淆规则进行配置,若无法确定则整个包名都进行保留;
  • 运用了反射的类不混淆;
  • 使用了 Gson 之类的工具要使 JavaBean 类即实体类不被混淆;
  • 有用到 WebView 的 JS 调用也需要保证写的接口方法不混淆;
  • 继承了Serializable接口的类不混淆;
  • Parcelable 的子类和 Creator 静态成员变量不混淆,否则会产生 Android.os.BadParcelableException 异常;
  • 使用的四大组件,自定义的Application* 实体类
  • JNI中调用的类
  • Layout布局使用的View构造函数(自定义控件)、android:onClick等。

 

常见问题

 

包冲突

因为混淆会使用无意义的a、b、c等字母对类、类成员进行替换,这就导致在多包集成的情况存在包名冲突的可能;
这里就需要我们在打包的时候添加 -repackageclasses 规则,将所有文件迁移至自定义的包名下,避免冲突;

 

运行中数据为null

这个没啥好说的,仔细检查下混淆配置是否完全吧,肯定是混淆了不该混淆的类;

 

Unsupported version number [55.0] (maximum 54.0, Java 10)

ProGuard版本不匹配;
在根目录的build.gradle中指定版本

buildscript{dependencies{classpath 'net.sf.proguard:proguard-gradle:6.1.1'}
}

 

通用配置

最后再提供一份通用的配置,这里的配置可能和系统默认混淆配置文件中存在重复(勿喷);

#---------------------------------基本指令区----------------------------------
# 设置混淆的压缩比率 0 ~ 7
-optimizationpasses 5
#混合时不使用大小写混合,混合后的类名为小写,windows下必须使用该选项
-dontusemixedcaseclassnames
#指定不去忽略非公共库的类和成员
-dontskipnonpubliclibraryclassmembers
#生成map文件
-printmapping proguardMapping.txt
##不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify,去掉这一步能够加快混淆速度。
#-dontpreverify
# 混淆采用的算法.
-optimizations !code/simplification/cast,!field/*,!class/merging/*
#避免混淆注解类
-dontwarn android.annotation
-keepattributes *Annotation*
#保留内部类
-keepattributes InnerClasses
#避免混淆泛型
-keepattributes Signature
#抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable
# 将.class信息中的类名重新定义为"Proguard"字符串
-renamesourcefileattribute Proguard
#----------------------------------------------------------------------------#保留枚举类不被混淆
-keepclassmembers enum * {public static **[] values();public static ** valueOf(java.lang.String);
}#保留Serializable序列化的类不被混淆
-keepclassmembers class * implements java.io.Serializable {static final long serialVersionUID;private static final java.io.ObjectStreamField[] serialPersistentFields;private void writeObject(java.io.ObjectOutputStream);private void readObject(java.io.ObjectInputStream);java.lang.Object writeReplace();java.lang.Object readResolve();
}
-keep public class * implements java.io.Serializable {*;}#----------------------Android通用-----------------
# 避免混淆Android基本组件,下面是兼容性比较高的规则
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Fragment
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
#-keep public class com.android.vending.licensing.ILicensingService# 保留support下的所有类及其内部类
-keep class android.support.** {*;}
-keep interface android.support.** {*;}
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**
-dontwarn android.support.**# 保留androidx下的所有类及其内部类
-keep class androidx.** {*;}
-keep public class * extends androidx.**
-keep interface androidx.** {*;}
-keep class com.google.android.material.** {*;}
-dontwarn androidx.**
-dontwarn com.google.android.material.**
-dontnote com.google.android.material.**# 保持Activity中与View相关方法不被混淆
-keepclassmembers class * extends android.app.Activity{public void *(android.view.View);
}# 避免混淆所有native的方法,涉及到C、C++
-keepclasseswithmembernames class * {native <methods>;
}# 避免混淆自定义控件类的get/set方法和构造函数
-keep public class * extends android.view.View{*** get*();void set*(***);public <init>(android.content.Context);public <init>(android.content.Context,android.util.AttributeSet);public <init>(android.content.Context,android.util.AttributeSet,int);
}
-keepclasseswithmembers class * {public <init>(android.content.Context, android.util.AttributeSet);public <init>(android.content.Context, android.util.AttributeSet, int);
}# 避免混淆枚举类
-keepclassmembers enum * {public static **[] values();public static ** valueOf(java.lang.String);
}# 避免混淆序列化类
# 不混淆Parcelable和它的实现子类,还有Creator成员变量
-keep class * implements android.os.Parcelable {public static final android.os.Parcelable$Creator *;
}# 不混淆Serializable和它的实现子类、其成员变量
-keep public class * implements java.io.Serializable {*;}
-keepclassmembers class * implements java.io.Serializable {static final long serialVersionUID;private static final java.io.ObjectStreamField[] serialPersistentFields;private void writeObject(java.io.ObjectOutputStream);private void readObject(java.io.ObjectInputStream);java.lang.Object writeReplace();java.lang.Object readResolve();
}# 资源ID不被混淆
-keep class **.R$* {*;}# 回调函数事件不能混淆
-keepclassmembers class * {void *(**On*Event);void *(**On*Listener);
}# Webview 相关不混淆
-keepclassmembers class fqcn.of.javascript.interface.for.webview {public *;
}
-keepclassmembers class * extends android.webkit.WebViewClient {public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.WebViewClient {public void *(android.webkit.WebView, java.lang.String);}# 使用GSON、fastjson等框架时,所写的JSON对象类不混淆,否则无法将JSON解析成对应的对象
-keepclassmembers class * {public <init>(org.json.JSONObject);
}#kotlin 相关
-dontwarn kotlin.**
-keep class kotlin.** { *; }
-keep interface kotlin.** { *; }
-keepclassmembers class kotlin.Metadata {public <methods>;
}
-keepclasseswithmembers @kotlin.Metadata class * { *; }
-keepclassmembers class **.WhenMappings {<fields>;
}
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
}-keep class kotlinx.** { *; }
-keep interface kotlinx.** { *; }
-dontwarn kotlinx.**
-dontnote kotlinx.serialization.SerializationKt-keep class org.jetbrains.** { *; }
-keep interface org.jetbrains.** { *; }
-dontwarn org.jetbrains.**#需要log分析问题
#(可选)避免Log打印输出
-assumenosideeffects class android.util.Log {public static boolean isLoggable(java.lang.String,int);public static *** d(...);public static *** e(...);public static *** i(...);public static *** v(...);public static *** println(...);public static *** w(...);public static *** wtf(...);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/591572.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

canvas绘制网格线示例

查看专栏目录 canvas示例教程100专栏&#xff0c;提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重…

docker 部署教学版本

文章目录 一、docker使用场景及常用命令1&#xff09;docker使用场景2&#xff09;rocky8(centos8)安装 docker3&#xff09;docker 常用命令补充常用命令 二、 单独部署每个镜像&#xff0c;部署spring 应用镜像推荐&#xff08;2023-12-18&#xff09;1、 安装使用 mysql1.1 …

高可用解决方案 Keepalived 概述

概述 Keepalived 介绍 Keepalived 是 Linux 下一个轻量级别的高可用解决方案&#xff0c;通过 **VRRP 协议&#xff08;虚拟路由冗余协议&#xff09;**来实现服务或者网络的高可用&#xff0c;可以利用其来解决单点故障。 起初是为 LVS 设计的&#xff0c;一个 LVS 服务会有 …

计算机毕业论文内容参考|基于智能搜索引擎的图书管理系统的设计与实现

文章目录 摘要前言绪论课题背景国内外现状与趋势课题内容相关技术与方法介绍系统分析系统设计系统实现系统测试总结与展望摘要 本文介绍了基于智能搜索引擎的图书管理系统的设计与实现。该系统旨在提供一个高效、智能化的图书管理平台,帮助用户更快、更准确地找到所需的图书资…

2024年PMP报考需要什么条件?怎么报名?

一、PMP是什么 PMP 是项目管理的入门级证书&#xff0c;全称是项目管理专业人士资格认证&#xff0c;由美国项目管理协会&#xff08;PMI&#xff09;举办的&#xff0c;受到全球200多个国家的认可&#xff0c;从1999 年到现在已经有20多年发展历史了。 顾名思义&#xff0c;…

【大数据面试知识点】分区器Partitioner:HashPartitioner、RangePartitioner

Spark HashParitioner的弊端是什么&#xff1f; HashPartitioner分区的原理很简单&#xff0c;对于给定的key&#xff0c;计算其hashCode&#xff0c;并除于分区的个数取余&#xff0c;如果余数小于0&#xff0c;则用余数分区的个数&#xff0c;最后返回的值就是这个key所属的…

阶段十-分布式-nginx服务器

一、Nginx简介 Nginx 是高性能的 HTTP 和反向代理的服务器&#xff0c;处理高并发能力是十分强大的&#xff0c;能经受高负载的考验,有报告表明能支持高达 50,000 个并发连接数。tomcat并发数量理论值是500&#xff0c;实际也就300左右。 1.2 正向代理 正向代理代理的是客户…

世微 DW01 4.2V锂电池保护电路芯片 专业电源管理芯片

一、 描述 DW01A 是一个锂电池保护电路&#xff0c;为避免锂电池因过充电、过放电、电流过大导致电池寿命缩短或电池被损坏而设计的。它具有高精确度的电压检测与时间延迟电路。 二、 主要特点 工作电流低&#xff1b; 过充检测 4.3V&#xff0c;过充释放 4.05V&#xff1b; 过…

缅怀一代传奇!TVP创始委员陈皓与他的《左耳听风:传奇程序员练级攻略》

引言 中文技术圈时常被一种浮躁所困扰。互联网企业历经跑马圈地的红利期后&#xff0c;开始在精细化运营的路上艰难求索&#xff1b;圈子里的程序员们&#xff0c;也被日益放缓的业务需求和不断内卷的行业态势所影响&#xff0c;职业困境、年龄危机成了老生常谈的话题。 成长往…

three.js 多通道组合

效果&#xff1a; 代码&#xff1a; <template><div><el-container><el-main><div class"box-card-left"><div id"threejs" style"border: 1px solid red"></div><div style"border: 1px so…

AI大模型引领未来智慧科研暨ChatGPT在地学、GIS、气象、农业、生态、环境等领域中的高级应用

以ChatGPT、LLaMA、Gemini、DALLE、Midjourney、Stable Diffusion、星火大模型、文心一言、千问为代表AI大语言模型带来了新一波人工智能浪潮&#xff0c;可以面向科研选题、思维导图、数据清洗、统计分析、高级编程、代码调试、算法学习、论文检索、写作、翻译、润色、文献辅助…

C语言中关于while语句的理解以及getchar和putchar

while是一个循环语句&#xff0c;关于while的一些理解可以看下面这串代码 #include <stdio.h> int main() {int i 0;scanf("%d", &i);printf("输入十以内的数字&#xff0c;从输入的数字开始一直数到十&#xff1a;");while (i<10){printf(…

php ext-sodium 拓展安装 linux+windows

php编译安装(linux)&#xff0c;可以参考&#xff1a;php编译安装 一、windows soduim源码包自带&#xff0c;直接修改php.ini&#xff0c;取消extensionsodium注释即可 二、linux 1.安装依赖 apt-get install libsodium-dev2.进入源码目录 这里写自己的源码目录 cd /us…

6种版本的并查集(java实现版)

目录 引入 并查集的具体讲解及代码实现 Quick Find Quick Union 基于size的优化 代码实现 基于rank的优化 代码实现 路径压缩 代码实现 更多关于路径压缩的并查集 引入 由孩子指向父亲的这种特殊的树结构可以很高效的处理连接问题&#xff0c;在一个复杂的图中&…

音视频技术开发周刊 | 326

每周一期&#xff0c;纵览音视频技术领域的干货。 新闻投稿&#xff1a;contributelivevideostack.com。 全球最强「开源版Gemini」诞生&#xff01;全能多模态模型Emu2登热榜&#xff0c;多项任务刷新SOTA 最强的全能多模态模型来了&#xff01;就在近日&#xff0c;智源研究院…

使用echarts的bmap配置项绘制区域轮廓遮罩

示例图 代码 <template><div id"map" style"width: 100%; height: 100vh"></div> </template><script> import * as echarts from "echarts"; import "echarts/extension/bmap/bmap"; export default…

华为交换机入门(六):VLAN的配置

VLAN&#xff08;Virtual Local Area Network&#xff09;即虚拟局域网&#xff0c;是将一个物理的LAN在逻辑上划分成多个广播域的通信技术。VLAN内的主机间可以直接通信&#xff0c;而VLAN间不能直接互通&#xff0c;从而将广播报文限制在一个VLAN内。 VLAN 主要用来解决如何…

企业工商信息数据哪里获取?工商全量信息有什么渠道?

随着互联网的发展和普及&#xff0c;越来越多的企业选择在网上进行业务推广和品牌宣传。对于一些想要了解企业工商信息的用户来说&#xff0c;如何获取企业工商信息数据成了一个非常重要的问题。下面分享获取企业工商全量信息的渠道和方式&#xff1a; 首先&#xff0c;我们可以…

浏览器---善用的一些调试技巧

https://www.cnblogs.com/dasusu/p/17932742.html

记一次Oracle Cloud计算实例ssh恢复过程

#ssh秘钥丢失# &#xff0c; #Oracle Cloud# 。 电脑上的ssh秘钥文件不知道什么时候丢失了&#xff0c;直到用的时候才发现没有了&#xff0c;这下可好&#xff0c;Oracle Cloud的计算实例连不上了&#xff0c;这个实例只能通过ssh连接上去&#xff1a; 以下是解决步骤&#x…